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,680 @@
1
+ ## Configuration
2
+
3
+ `tantan-typeorm-gs` is configured through `createGoogleSheetsDataSource()`.
4
+
5
+ ### Basic Configuration
6
+
7
+ A minimal configuration requires:
8
+
9
+ - `type`
10
+ - `spreadsheetId`
11
+ - `credentials`
12
+
13
+ Example:
14
+
15
+ ```ts
16
+ import { createGoogleSheetsDataSource } from "tantan-typeorm-gs";
17
+
18
+ const dataSource = createGoogleSheetsDataSource({
19
+ type: "google-sheets",
20
+
21
+ spreadsheetId: process.env.GOOGLE_SHEETS_SPREADSHEET_ID!,
22
+
23
+ credentials: {
24
+ clientEmail: process.env.GOOGLE_SHEETS_CLIENT_EMAIL!,
25
+
26
+ privateKey: process.env.GOOGLE_SHEETS_PRIVATE_KEY!
27
+ },
28
+
29
+ entities: [User]
30
+ });
31
+ ```
32
+
33
+ Initialize the data source before using repositories:
34
+
35
+ ```ts
36
+ await dataSource.initialize();
37
+
38
+ const repository = dataSource.getRepository(User);
39
+ ```
40
+
41
+ ### Configuration Options
42
+
43
+ #### `type`
44
+
45
+ Identifies the database driver.
46
+
47
+ ```ts
48
+ type: "google-sheets";
49
+ ```
50
+
51
+ This value is required.
52
+
53
+ ---
54
+
55
+ #### `spreadsheetId`
56
+
57
+ The ID of the Google Spreadsheet used by the driver.
58
+
59
+ ```ts
60
+ spreadsheetId: "1AbCdEfGhIjKlMnOpQrStUvWxYz";
61
+ ```
62
+
63
+ This value is required.
64
+
65
+ ---
66
+
67
+ #### `credentials`
68
+
69
+ Google service-account credentials.
70
+
71
+ ```ts
72
+ credentials: {
73
+ clientEmail:
74
+ 'service-account@project-id.iam.gserviceaccount.com',
75
+
76
+ privateKey:
77
+ '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n',
78
+ }
79
+ ```
80
+
81
+ Both `clientEmail` and `privateKey` are required.
82
+
83
+ For security reasons, credentials should normally come from environment variables or a secret-management system.
84
+
85
+ ---
86
+
87
+ #### `client`
88
+
89
+ A custom `GoogleSheetsClient` implementation can be supplied instead of creating the default Google API client.
90
+
91
+ ```ts
92
+ client: customClient;
93
+ ```
94
+
95
+ When `client` is provided, the driver uses that client for Google Sheets operations.
96
+
97
+ This is useful for:
98
+
99
+ - testing
100
+ - mocking
101
+ - custom Google Sheets implementations
102
+ - applications that already manage Google Sheets access themselves
103
+
104
+ The default client is created automatically when `client` is omitted.
105
+
106
+ ---
107
+
108
+ #### `entities`
109
+
110
+ TypeORM entities used by the data source.
111
+
112
+ ```ts
113
+ entities: [User, Product];
114
+ ```
115
+
116
+ Entities define the worksheet metadata used by the driver.
117
+
118
+ Example:
119
+
120
+ ```ts
121
+ @Entity("users")
122
+ class User {
123
+ @PrimaryGeneratedColumn()
124
+ id!: number;
125
+
126
+ @Column()
127
+ name!: string;
128
+
129
+ @Column()
130
+ email!: string;
131
+ }
132
+ ```
133
+
134
+ ---
135
+
136
+ #### `subscribers`
137
+
138
+ TypeORM subscribers can be registered through the data source configuration.
139
+
140
+ ```ts
141
+ subscribers: [UserSubscriber];
142
+ ```
143
+
144
+ Subscribers can participate in supported TypeORM entity lifecycle events.
145
+
146
+ ---
147
+
148
+ #### `migrations`
149
+
150
+ Migration classes can be supplied through the TypeORM data source configuration.
151
+
152
+ ```ts
153
+ migrations: [
154
+ // migration classes
155
+ ];
156
+ ```
157
+
158
+ Migration support should be considered separately from Google Sheets' own data model because Google Sheets does not provide relational database schema semantics.
159
+
160
+ ---
161
+
162
+ #### `synchronize`
163
+
164
+ Controls TypeORM schema synchronization.
165
+
166
+ ```ts
167
+ synchronize: true;
168
+ ```
169
+
170
+ When enabled, TypeORM synchronization is used to create or update the worksheet structure represented by the entity metadata.
171
+
172
+ For production environments, review synchronization behavior carefully before enabling it.
173
+
174
+ ---
175
+
176
+ #### `migrationsRun`
177
+
178
+ Controls whether configured migrations are automatically executed during data-source initialization.
179
+
180
+ ```ts
181
+ migrationsRun: true;
182
+ ```
183
+
184
+ Use this only when migrations have been configured and the application's migration strategy requires automatic execution.
185
+
186
+ ---
187
+
188
+ #### `dropSchema`
189
+
190
+ Controls whether the configured schema is dropped during initialization.
191
+
192
+ ```ts
193
+ dropSchema: true;
194
+ ```
195
+
196
+ This option is destructive and should generally not be enabled against a production spreadsheet.
197
+
198
+ ---
199
+
200
+ #### `logging`
201
+
202
+ TypeORM logging configuration can be passed through to the data source.
203
+
204
+ For example:
205
+
206
+ ```ts
207
+ logging: true;
208
+ ```
209
+
210
+ Or:
211
+
212
+ ```ts
213
+ logging: ["query", "error"];
214
+ ```
215
+
216
+ Use logging carefully when credentials or sensitive spreadsheet data may appear in application logs.
217
+
218
+ ---
219
+
220
+ #### `logger`
221
+
222
+ A custom TypeORM logger can be supplied.
223
+
224
+ ```ts
225
+ logger: customLogger;
226
+ ```
227
+
228
+ This allows applications to integrate driver logging with their existing logging infrastructure.
229
+
230
+ ---
231
+
232
+ #### `name`
233
+
234
+ An optional data-source name.
235
+
236
+ ```ts
237
+ name: "google-sheets";
238
+ ```
239
+
240
+ This can be useful when an application manages multiple data sources.
241
+
242
+ ### Full Example
243
+
244
+ A configuration can combine the available options:
245
+
246
+ ```ts
247
+ const dataSource = createGoogleSheetsDataSource({
248
+ type: "google-sheets",
249
+
250
+ spreadsheetId: process.env.GOOGLE_SHEETS_SPREADSHEET_ID!,
251
+
252
+ credentials: {
253
+ clientEmail: process.env.GOOGLE_SHEETS_CLIENT_EMAIL!,
254
+
255
+ privateKey: process.env.GOOGLE_SHEETS_PRIVATE_KEY!
256
+ },
257
+
258
+ entities: [User, Product],
259
+
260
+ subscribers: [UserSubscriber],
261
+
262
+ synchronize: true,
263
+
264
+ logging: ["query", "error"],
265
+
266
+ name: "google-sheets"
267
+ });
268
+ ```
269
+
270
+ ### Custom Client
271
+
272
+ The `client` option takes precedence over the automatically created Google Sheets API client.
273
+
274
+ ```text
275
+ client provided
276
+
277
+
278
+ use supplied GoogleSheetsClient
279
+
280
+
281
+ GoogleSheetsDriver
282
+
283
+ client omitted
284
+
285
+
286
+ create GoogleSheetsApiClient
287
+
288
+
289
+ GoogleSheetsDriver
290
+ ```
291
+
292
+ See the **Custom Client** section for more details.
293
+
294
+ ### Lifecycle
295
+
296
+ The typical application lifecycle is:
297
+
298
+ ```ts
299
+ const dataSource = createGoogleSheetsDataSource({
300
+ // configuration
301
+ });
302
+
303
+ await dataSource.initialize();
304
+
305
+ const repository = dataSource.getRepository(User);
306
+
307
+ // use repository
308
+
309
+ await dataSource.destroy();
310
+ ```
311
+
312
+ The data source should be destroyed when the application no longer needs it, especially in scripts, tests, and short-lived processes.
313
+
314
+ ## Entity Definition
315
+
316
+ `tantan-typeorm-gs` uses standard TypeORM entity definitions to describe the structure of Google Sheets data.
317
+
318
+ An entity represents a worksheet, while entity columns represent worksheet columns.
319
+
320
+ ### Basic Entity
321
+
322
+ A basic entity can be defined using TypeORM decorators:
323
+
324
+ ```ts
325
+ import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
326
+
327
+ @Entity("users")
328
+ class User {
329
+ @PrimaryGeneratedColumn()
330
+ id!: number;
331
+
332
+ @Column()
333
+ name!: string;
334
+
335
+ @Column()
336
+ email!: string;
337
+ }
338
+ ```
339
+
340
+ In this example:
341
+
342
+ ```text
343
+ Entity
344
+ User
345
+
346
+ Worksheet
347
+ users
348
+
349
+ Columns
350
+ id
351
+ name
352
+ email
353
+ ```
354
+
355
+ The worksheet should contain a header row corresponding to the entity columns.
356
+
357
+ For example:
358
+
359
+ ```text
360
+ | id | name | email |
361
+ |----|------|-------|
362
+ | 1 | Budi | budi@example.com |
363
+ | 2 | Siti | siti@example.com |
364
+ ```
365
+
366
+ ### Worksheet Name
367
+
368
+ The worksheet name can be specified with `@Entity()`:
369
+
370
+ ```ts
371
+ @Entity("users")
372
+ class User {
373
+ // ...
374
+ }
375
+ ```
376
+
377
+ The value passed to `@Entity()` is used as the entity's custom table name and therefore determines the worksheet name used by the driver.
378
+
379
+ If no explicit name is supplied:
380
+
381
+ ```ts
382
+ @Entity()
383
+ class User {
384
+ // ...
385
+ }
386
+ ```
387
+
388
+ TypeORM metadata determines the resulting table name.
389
+
390
+ ### Columns
391
+
392
+ Entity properties are mapped to worksheet columns using `@Column()`:
393
+
394
+ ```ts
395
+ @Column()
396
+ name!: string;
397
+
398
+ @Column()
399
+ email!: string;
400
+ ```
401
+
402
+ The property name is used as the column name unless TypeORM metadata specifies another database column name.
403
+
404
+ A custom column name can be defined:
405
+
406
+ ```ts
407
+ @Column({
408
+ name: 'full_name',
409
+ })
410
+ name!: string;
411
+ ```
412
+
413
+ The worksheet header then uses:
414
+
415
+ ```text
416
+ full_name
417
+ ```
418
+
419
+ instead of:
420
+
421
+ ```text
422
+ name
423
+ ```
424
+
425
+ ### Primary Column
426
+
427
+ A manually assigned primary key can be defined with `@PrimaryColumn()`:
428
+
429
+ ```ts
430
+ @Entity("users")
431
+ class User {
432
+ @PrimaryColumn()
433
+ id!: number;
434
+
435
+ @Column()
436
+ name!: string;
437
+ }
438
+ ```
439
+
440
+ When inserting a row, the primary key must be provided.
441
+
442
+ For example:
443
+
444
+ ```ts
445
+ await repository.insert({
446
+ id: 1,
447
+ name: "Budi"
448
+ });
449
+ ```
450
+
451
+ An insert without the required primary key is rejected by the driver.
452
+
453
+ ### Generated Primary Column
454
+
455
+ Auto-generated primary keys can be defined with `@PrimaryGeneratedColumn()`:
456
+
457
+ ```ts
458
+ @Entity("users")
459
+ class User {
460
+ @PrimaryGeneratedColumn()
461
+ id!: number;
462
+
463
+ @Column()
464
+ name!: string;
465
+ }
466
+ ```
467
+
468
+ The default generated strategy is supported by the driver.
469
+
470
+ When inserting multiple rows:
471
+
472
+ ```ts
473
+ await repository.insert([
474
+ {
475
+ name: "Budi"
476
+ },
477
+ {
478
+ name: "Siti"
479
+ }
480
+ ]);
481
+ ```
482
+
483
+ The driver assigns generated IDs to rows that do not provide an explicit generated primary key.
484
+
485
+ ### Explicit Generated Primary Keys
486
+
487
+ An explicitly supplied generated primary key is preserved:
488
+
489
+ ```ts
490
+ await repository.insert({
491
+ id: 100,
492
+ name: "Budi"
493
+ });
494
+ ```
495
+
496
+ The driver does not replace the supplied ID with another generated value.
497
+
498
+ If the explicit primary key already exists in the worksheet, the insert is rejected as a duplicate primary-key operation.
499
+
500
+ ### UUID Primary Keys
501
+
502
+ UUID generation is supported through TypeORM's generated primary-column metadata:
503
+
504
+ ```ts
505
+ @Entity("users")
506
+ class User {
507
+ @PrimaryGeneratedColumn("uuid")
508
+ id!: string;
509
+
510
+ @Column()
511
+ name!: string;
512
+ }
513
+ ```
514
+
515
+ When the ID is omitted during insertion, the driver generates a UUID.
516
+
517
+ ```ts
518
+ await repository.insert({
519
+ name: "Budi"
520
+ });
521
+ ```
522
+
523
+ ### Unsupported Generated Strategies
524
+
525
+ Not every relational database generation strategy has an equivalent in Google Sheets.
526
+
527
+ The driver does not support generated primary-key strategies that depend on database-specific row identity mechanisms, such as:
528
+
529
+ ```ts
530
+ @PrimaryGeneratedColumn('identity')
531
+ ```
532
+
533
+ and:
534
+
535
+ ```ts
536
+ @PrimaryGeneratedColumn('rowid')
537
+ ```
538
+
539
+ These strategies are rejected by the driver rather than being emulated.
540
+
541
+ ### Dates
542
+
543
+ TypeORM date metadata can be used for supported entity date columns.
544
+
545
+ Lifecycle-managed date columns such as create-date and update-date columns are populated by the driver during insert operations when the corresponding TypeORM metadata is present.
546
+
547
+ Example:
548
+
549
+ ```ts
550
+ @Entity("users")
551
+ class User {
552
+ @PrimaryGeneratedColumn()
553
+ id!: number;
554
+
555
+ @Column()
556
+ name!: string;
557
+
558
+ @CreateDateColumn()
559
+ createdAt!: Date;
560
+
561
+ @UpdateDateColumn()
562
+ updatedAt!: Date;
563
+ }
564
+ ```
565
+
566
+ ### Relations
567
+
568
+ Relations can be defined using normal TypeORM relation decorators:
569
+
570
+ ```ts
571
+ @Entity("posts")
572
+ class Post {
573
+ @PrimaryGeneratedColumn()
574
+ id!: number;
575
+
576
+ @Column()
577
+ title!: string;
578
+
579
+ @Column()
580
+ authorId!: number;
581
+
582
+ @ManyToOne(() => User, {
583
+ nullable: false
584
+ })
585
+ author!: User;
586
+ }
587
+ ```
588
+
589
+ The driver supports relation metadata resolution and supported relation loading.
590
+
591
+ However, Google Sheets does not provide database-level foreign-key constraints or referential-integrity enforcement.
592
+
593
+ Therefore, defining a TypeORM relation does not create a foreign-key constraint inside Google Sheets.
594
+
595
+ ### Naming Strategy
596
+
597
+ TypeORM naming strategies can affect the names generated from entity metadata.
598
+
599
+ For example, a naming strategy may transform a column name:
600
+
601
+ ```text
602
+ displayName
603
+
604
+ col_displayname
605
+ ```
606
+
607
+ The driver uses the resulting TypeORM metadata when resolving worksheet and column names.
608
+
609
+ This allows applications to continue using their existing TypeORM naming strategy where supported.
610
+
611
+ ### Entity Metadata
612
+
613
+ The driver relies on TypeORM metadata rather than requiring a separate Google Sheets schema definition.
614
+
615
+ The resulting metadata determines information such as:
616
+
617
+ - worksheet name
618
+ - column names
619
+ - primary columns
620
+ - generated columns
621
+ - create-date columns
622
+ - update-date columns
623
+ - relations
624
+ - naming-strategy transformations
625
+
626
+ This keeps entity definitions compatible with the normal TypeORM programming model.
627
+
628
+ ### Example
629
+
630
+ A complete entity can look like:
631
+
632
+ ```ts
633
+ import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
634
+
635
+ @Entity("users")
636
+ class User {
637
+ @PrimaryGeneratedColumn()
638
+ id!: number;
639
+
640
+ @Column()
641
+ name!: string;
642
+
643
+ @Column()
644
+ email!: string;
645
+
646
+ @CreateDateColumn()
647
+ createdAt!: Date;
648
+
649
+ @UpdateDateColumn()
650
+ updatedAt!: Date;
651
+ }
652
+ ```
653
+
654
+ Register the entity with the data source:
655
+
656
+ ```ts
657
+ const dataSource = createGoogleSheetsDataSource({
658
+ type: "google-sheets",
659
+
660
+ spreadsheetId: process.env.GOOGLE_SHEETS_SPREADSHEET_ID!,
661
+
662
+ credentials: {
663
+ clientEmail: process.env.GOOGLE_SHEETS_CLIENT_EMAIL!,
664
+
665
+ privateKey: process.env.GOOGLE_SHEETS_PRIVATE_KEY!
666
+ },
667
+
668
+ entities: [User]
669
+ });
670
+ ```
671
+
672
+ After initialization, the repository can be obtained normally:
673
+
674
+ ```ts
675
+ await dataSource.initialize();
676
+
677
+ const repository = dataSource.getRepository(User);
678
+ ```
679
+
680
+ The repository can then be used for CRUD operations against the corresponding worksheet.