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,243 @@
1
+ ## Development Guide
2
+
3
+ This guide describes the development workflow for contributing to `tantan-typeorm-gs`.
4
+
5
+ ### Requirements
6
+
7
+ The project uses:
8
+
9
+ - Bun
10
+ - TypeScript
11
+ - TypeORM
12
+ - Google Sheets API
13
+
14
+ Install the project dependencies:
15
+
16
+ ```bash id="7m2x4p"
17
+ bun install
18
+ ```
19
+
20
+ ### Project Structure
21
+
22
+ The project is organized around several main layers:
23
+
24
+ ```text id="5q8n1v"
25
+ src/
26
+ ├── client/
27
+ │ └── index.ts
28
+ │
29
+ └── core/
30
+ ├── driver.ts
31
+ ├── memory.ts
32
+ ├── data-source.ts
33
+ ├── schema-builder.ts
34
+ ├── types.ts
35
+ ├── utils.ts
36
+ └── query/
37
+ ├── interpreter.ts
38
+ ├── runner.ts
39
+ └── types.ts
40
+
41
+
42
+ ```
43
+
44
+ The main execution flow is:
45
+
46
+ ```text id="3v7k9c"
47
+ TypeORM Repository
48
+ ↓
49
+ QueryBuilder
50
+ ↓
51
+ GoogleSheetsQueryRunner
52
+ ↓
53
+ GoogleSheetsQueryInterpreter
54
+ ↓
55
+ GoogleSheetsClient
56
+ ↓
57
+ Google Sheets API
58
+ ```
59
+
60
+ ### Development Principles
61
+
62
+ Changes should be made at the layer responsible for the behavior.
63
+
64
+ For example:
65
+
66
+ | Concern | Main layer |
67
+ | ------------------------ | ------------------------------ |
68
+ | Google API communication | `GoogleSheetsApiClient` |
69
+ | Driver behavior | `GoogleSheetsDriver` |
70
+ | SQL/query interpretation | `GoogleSheetsQueryInterpreter` |
71
+ | Query execution | `GoogleSheetsQueryRunner` |
72
+ | Data source registration | `GoogleSheetsDataSource` |
73
+ | Driver errors | `error.ts` |
74
+
75
+ Avoid implementing the same behavior in multiple layers.
76
+
77
+ ### Running Tests
78
+
79
+ Run the complete test suite:
80
+
81
+ ```bash id="8c4m2x"
82
+ bun test
83
+ ```
84
+
85
+ Run TypeScript type checking:
86
+
87
+ ```bash id="1v7n5q"
88
+ bun run typecheck
89
+ ```
90
+
91
+ Individual test files can also be executed directly:
92
+
93
+ ```bash id="6m3k9p"
94
+ bun test test/google-sheets/0001-client.test.ts
95
+ ```
96
+
97
+ Performance tests are kept separate:
98
+
99
+ ```bash id="4q8x1c"
100
+ bun test test/google-sheets/0007-performance.test.ts
101
+ ```
102
+
103
+ ### Test Strategy
104
+
105
+ The project separates driver tests from Google API integration tests.
106
+
107
+ Driver and repository behavior should generally use `Memory`.
108
+
109
+ ```ts id="9p2v6m"
110
+ const client = new Memory({
111
+ users: []
112
+ });
113
+ ```
114
+
115
+ This keeps tests fast and deterministic.
116
+
117
+ The real `GoogleSheetsApiClient` is used for API integration tests.
118
+
119
+ ### Adding a New Feature
120
+
121
+ When implementing a new feature:
122
+
123
+ 1. Identify the responsible layer.
124
+ 2. Add or update the relevant test.
125
+ 3. Implement the smallest required production change.
126
+ 4. Run the focused test.
127
+ 5. Run type checking.
128
+ 6. Run the complete test suite.
129
+ 7. Update the documentation when the supported behavior changes.
130
+
131
+ A typical workflow is:
132
+
133
+ ```bash id="2x6m8q"
134
+ bun test <focused-test>
135
+ bun run typecheck
136
+ bun test
137
+ ```
138
+
139
+ ### Avoid Unnecessary Changes
140
+
141
+ The driver has multiple compatibility layers with TypeORM.
142
+
143
+ A seemingly small change can affect existing repository behavior.
144
+
145
+ When a test already covers stable behavior, avoid changing its implementation unless a concrete regression or missing requirement has been identified.
146
+
147
+ Prefer targeted changes over broad refactoring.
148
+
149
+ ### Adding Tests
150
+
151
+ Tests should verify behavior rather than implementation details whenever possible.
152
+
153
+ For example, repository behavior should preferably be tested through:
154
+
155
+ ```ts id="5n9q3v"
156
+ const repository = dataSource.getRepository(User);
157
+
158
+ const users = await repository.find();
159
+ ```
160
+
161
+ Direct `QueryRunner` tests are appropriate when behavior cannot be reliably exercised through the repository API.
162
+
163
+ This is particularly useful for low-level driver validation such as explicit primary-key handling.
164
+
165
+ ### Performance Tests
166
+
167
+ Performance tests should remain separate from functional tests.
168
+
169
+ The existing performance baseline uses `Memory` and measures operations over 1,000 rows.
170
+
171
+ Performance measurements should be treated as regression indicators rather than absolute guarantees.
172
+
173
+ ### Integration Tests
174
+
175
+ Changes to the Google Sheets API client should also be validated against the real Google Sheets API where appropriate.
176
+
177
+ Use a dedicated test spreadsheet and test credentials.
178
+
179
+ Never use production spreadsheet data for automated integration tests.
180
+
181
+ ### TypeScript
182
+
183
+ The project is written in TypeScript and should remain type-safe.
184
+
185
+ Before submitting changes, run:
186
+
187
+ ```bash id="7c4m1x"
188
+ bun run typecheck
189
+ ```
190
+
191
+ Avoid using `any` unless the TypeORM or Google API boundary genuinely requires it.
192
+
193
+ ### Debugging
194
+
195
+ When debugging driver behavior, trace the execution path from the repository toward the client:
196
+
197
+ ```text id="0v8n2q"
198
+ Repository
199
+ ↓
200
+ QueryBuilder
201
+ ↓
202
+ QueryRunner
203
+ ↓
204
+ Interpreter
205
+ ↓
206
+ Client
207
+ ```
208
+
209
+ This helps determine whether a problem originates from:
210
+
211
+ - TypeORM query generation
212
+ - query interpretation
213
+ - driver execution
214
+ - Google Sheets client behavior
215
+ - Google Sheets API behavior
216
+
217
+ ### Pull Request Checklist
218
+
219
+ Before submitting a change:
220
+
221
+ ```text id="3k7m1x"
222
+ [ ] Focused tests pass
223
+ [ ] TypeScript typecheck passes
224
+ [ ] Full test suite passes
225
+ [ ] Integration tests updated if necessary
226
+ [ ] Performance tests considered if relevant
227
+ [ ] Documentation updated if behavior changed
228
+ [ ] No credentials or sensitive data committed
229
+ [ ] No unrelated refactoring included
230
+ ```
231
+
232
+ ### Summary
233
+
234
+ The development process should prioritize:
235
+
236
+ - small, targeted changes
237
+ - behavior-driven tests
238
+ - clear separation between driver and API tests
239
+ - TypeScript type safety
240
+ - preserving existing compatibility
241
+ - documentation that reflects tested capabilities
242
+
243
+ The complete test suite should remain green before a change is considered complete.
@@ -0,0 +1,502 @@
1
+ # Entity Definition
2
+
3
+ `tantan-typeorm-gs` uses TypeORM entities to define the structure of data stored in Google Sheets.
4
+
5
+ Each entity represents a worksheet, and each entity property represents a column in that worksheet.
6
+
7
+ ## Basic Entity
8
+
9
+ A simple entity can be defined using TypeORM decorators:
10
+
11
+ ```ts
12
+ import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
13
+
14
+ @Entity("Users")
15
+ export class User {
16
+ @PrimaryGeneratedColumn()
17
+ id!: number;
18
+
19
+ @Column()
20
+ name!: string;
21
+
22
+ @Column()
23
+ age!: number;
24
+
25
+ @Column()
26
+ active!: boolean;
27
+ }
28
+ ```
29
+
30
+ The entity above is mapped to a worksheet named `Users`.
31
+
32
+ The corresponding worksheet structure is:
33
+
34
+ | id | name | age | active |
35
+ | --- | ---- | --- | ------ |
36
+ | 1 | Budi | 30 | TRUE |
37
+ | 2 | Siti | 25 | FALSE |
38
+
39
+ The worksheet name comes from the value passed to `@Entity()`.
40
+
41
+ ---
42
+
43
+ ## Entity Name
44
+
45
+ Use `@Entity()` to define the worksheet name:
46
+
47
+ ```ts
48
+ @Entity("Users")
49
+ export class User {
50
+ // ...
51
+ }
52
+ ```
53
+
54
+ The name passed to `@Entity()` is used as the worksheet name.
55
+
56
+ If the worksheet does not exist and synchronization is enabled, the driver can create it automatically.
57
+
58
+ See [Synchronization](synchronization.md) for more information.
59
+
60
+ ---
61
+
62
+ ## Columns
63
+
64
+ Use `@Column()` to define entity properties that are stored as worksheet columns:
65
+
66
+ ```ts
67
+ @Entity("Users")
68
+ export class User {
69
+ @PrimaryGeneratedColumn()
70
+ id!: number;
71
+
72
+ @Column()
73
+ name!: string;
74
+
75
+ @Column()
76
+ age!: number;
77
+
78
+ @Column()
79
+ active!: boolean;
80
+ }
81
+ ```
82
+
83
+ The property names are mapped to worksheet columns by TypeORM metadata.
84
+
85
+ The driver supports the following commonly used types:
86
+
87
+ | Type | Status |
88
+ | --------- | --------- |
89
+ | `string` | Supported |
90
+ | `number` | Supported |
91
+ | `boolean` | Supported |
92
+ | `Date` | Supported |
93
+ | `uuid` | Supported |
94
+ | `int` | Supported |
95
+
96
+ Google Sheets commonly returns cell values as strings. The driver uses TypeORM column metadata to hydrate values into their expected application types.
97
+
98
+ For example, a sheet value such as:
99
+
100
+ ```text
101
+ 30
102
+ ```
103
+
104
+ can be hydrated as:
105
+
106
+ ```ts
107
+ 30; // number
108
+ ```
109
+
110
+ when the entity property is declared as a numeric column.
111
+
112
+ ---
113
+
114
+ ## Primary Columns
115
+
116
+ Entities should define a primary column.
117
+
118
+ ### Generated Primary Key
119
+
120
+ Use `@PrimaryGeneratedColumn()` for automatically generated numeric IDs:
121
+
122
+ ```ts
123
+ @Entity("Users")
124
+ export class User {
125
+ @PrimaryGeneratedColumn()
126
+ id!: number;
127
+
128
+ @Column()
129
+ name!: string;
130
+ }
131
+ ```
132
+
133
+ The driver supports generated numeric IDs and UUIDs.
134
+
135
+ ### UUID Primary Key
136
+
137
+ A UUID can be used as a generated primary key:
138
+
139
+ ```ts
140
+ @Entity("Users")
141
+ export class User {
142
+ @PrimaryGeneratedColumn("uuid")
143
+ id!: string;
144
+
145
+ @Column()
146
+ name!: string;
147
+ }
148
+ ```
149
+
150
+ ### Explicit Primary Key
151
+
152
+ Use `@PrimaryColumn()` when the application provides the primary key:
153
+
154
+ ```ts
155
+ @Entity("Users")
156
+ export class User {
157
+ @PrimaryColumn()
158
+ id!: string;
159
+
160
+ @Column()
161
+ name!: string;
162
+ }
163
+ ```
164
+
165
+ Primary key values must be unique within the worksheet.
166
+
167
+ The driver does not provide database-level primary key enforcement like a relational database. Primary key validation is performed by the driver during supported operations.
168
+
169
+ ---
170
+
171
+ ## Column Options
172
+
173
+ Standard TypeORM column options can be used where supported by the driver.
174
+
175
+ For example:
176
+
177
+ ```ts
178
+ @Column({
179
+ nullable: true,
180
+ })
181
+ description!: string | null;
182
+ ```
183
+
184
+ Default values are also supported:
185
+
186
+ ```ts
187
+ @Column({
188
+ default: true,
189
+ })
190
+ active!: boolean;
191
+ ```
192
+
193
+ The driver applies the configured default when inserting entities where the value is not explicitly provided.
194
+
195
+ ---
196
+
197
+ ## Custom Column Names
198
+
199
+ TypeORM column names can be customized using the `name` option:
200
+
201
+ ```ts
202
+ @Entity("Users")
203
+ export class User {
204
+ @PrimaryGeneratedColumn()
205
+ id!: number;
206
+
207
+ @Column({
208
+ name: "full_name"
209
+ })
210
+ name!: string;
211
+ }
212
+ ```
213
+
214
+ The worksheet will use:
215
+
216
+ | id | full_name |
217
+ | --- | --------- |
218
+ | 1 | Budi |
219
+
220
+ The entity property remains `name`:
221
+
222
+ ```ts
223
+ user.name;
224
+ ```
225
+
226
+ while the worksheet column is:
227
+
228
+ ```text
229
+ full_name
230
+ ```
231
+
232
+ Column names are resolved through TypeORM's entity metadata.
233
+
234
+ ---
235
+
236
+ ## Date Columns
237
+
238
+ `Date` columns are supported:
239
+
240
+ ```ts
241
+ @Entity("Users")
242
+ export class User {
243
+ @PrimaryGeneratedColumn()
244
+ id!: number;
245
+
246
+ @Column()
247
+ name!: string;
248
+
249
+ @Column()
250
+ createdAt!: Date;
251
+ }
252
+ ```
253
+
254
+ When reading data, the driver hydrates the worksheet value into a JavaScript `Date` according to the column metadata.
255
+
256
+ ---
257
+
258
+ ## Soft Delete
259
+
260
+ Soft-delete entities can use TypeORM's `@DeleteDateColumn()`:
261
+
262
+ ```ts
263
+ import { Entity, PrimaryGeneratedColumn, Column, DeleteDateColumn } from "typeorm";
264
+
265
+ @Entity("Users")
266
+ export class User {
267
+ @PrimaryGeneratedColumn()
268
+ id!: number;
269
+
270
+ @Column()
271
+ name!: string;
272
+
273
+ @DeleteDateColumn({
274
+ nullable: true
275
+ })
276
+ deletedAt!: Date | null;
277
+ }
278
+ ```
279
+
280
+ The driver supports:
281
+
282
+ - `@DeleteDateColumn()`
283
+ - `softDelete()`
284
+ - `softRemove()`
285
+ - `restore()`
286
+ - Excluding soft-deleted rows from normal queries
287
+ - Including soft-deleted rows with `withDeleted`
288
+
289
+ For example:
290
+
291
+ ```ts
292
+ await userRepository.softDelete(user.id);
293
+ ```
294
+
295
+ Normal queries will exclude the soft-deleted entity:
296
+
297
+ ```ts
298
+ const users = await userRepository.find();
299
+ ```
300
+
301
+ To include soft-deleted entities:
302
+
303
+ ```ts
304
+ const users = await userRepository.find({
305
+ withDeleted: true
306
+ });
307
+ ```
308
+
309
+ See [CRUD Operations](crud.md) for more information.
310
+
311
+ ---
312
+
313
+ ## Relations
314
+
315
+ TypeORM relation metadata can be used with Google Sheets.
316
+
317
+ For example:
318
+
319
+ ```ts
320
+ @Entity("Users")
321
+ export class User {
322
+ @PrimaryGeneratedColumn()
323
+ id!: number;
324
+
325
+ @Column()
326
+ name!: string;
327
+
328
+ @OneToMany(() => Post, (post) => post.user)
329
+ posts!: Post[];
330
+ }
331
+
332
+ @Entity("Posts")
333
+ export class Post {
334
+ @PrimaryGeneratedColumn()
335
+ id!: number;
336
+
337
+ @Column()
338
+ title!: string;
339
+
340
+ @Column()
341
+ userId!: number;
342
+
343
+ @ManyToOne(() => User, (user) => user.posts)
344
+ @JoinColumn({
345
+ name: "userId"
346
+ })
347
+ user!: User;
348
+ }
349
+ ```
350
+
351
+ The `Posts` worksheet stores the foreign-key value in the `userId` column.
352
+
353
+ An explicit foreign-key value can be persisted normally:
354
+
355
+ ```ts
356
+ post.userId = user.id;
357
+
358
+ await postRepository.save(post);
359
+ ```
360
+
361
+ Relations can also be loaded explicitly:
362
+
363
+ ```ts
364
+ const users = await userRepository.find({
365
+ relations: {
366
+ posts: true
367
+ }
368
+ });
369
+ ```
370
+
371
+ Supported relation features include:
372
+
373
+ - `@ManyToOne`
374
+ - `@OneToMany`
375
+ - `@JoinColumn`
376
+ - Explicit foreign-key column persistence
377
+ - Explicit relation loading
378
+ - Relation queries through supported joins
379
+ - Soft-delete filtering for relations
380
+ - `withDeleted` relation loading
381
+
382
+ Nested relation persistence and cascade persistence are not fully supported.
383
+
384
+ For example, applications should not rely on automatically persisting a nested relation through:
385
+
386
+ ```ts
387
+ await userRepository.save({
388
+ name: "Budi",
389
+ posts: [
390
+ {
391
+ title: "Post 1"
392
+ }
393
+ ]
394
+ });
395
+ ```
396
+
397
+ Applications should explicitly persist related entities when necessary.
398
+
399
+ ---
400
+
401
+ ## Naming Strategies
402
+
403
+ Entity and column names are resolved through TypeORM metadata.
404
+
405
+ The driver does not implement a separate naming-strategy system. TypeORM-generated metadata is used to determine the worksheet and column names.
406
+
407
+ ---
408
+
409
+ ## Entity Metadata
410
+
411
+ Entity definitions are processed by TypeORM before the Google Sheets driver handles them.
412
+
413
+ The driver consumes TypeORM's `EntityMetadata`, including information such as:
414
+
415
+ - Entity name
416
+ - Worksheet name
417
+ - Column names
418
+ - Column types
419
+ - Primary columns
420
+ - Generated columns
421
+ - Delete-date columns
422
+ - Relations
423
+ - Join columns
424
+ - Nullable columns
425
+ - Default values
426
+
427
+ This means entity definitions remain standard TypeORM entity definitions rather than using a Google Sheets-specific schema format.
428
+
429
+ ---
430
+
431
+ ## Example
432
+
433
+ A complete entity setup might look like:
434
+
435
+ ```ts
436
+ import { Column, DeleteDateColumn, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
437
+
438
+ @Entity("Users")
439
+ export class User {
440
+ @PrimaryGeneratedColumn()
441
+ id!: number;
442
+
443
+ @Column()
444
+ name!: string;
445
+
446
+ @Column()
447
+ active!: boolean;
448
+
449
+ @DeleteDateColumn({
450
+ nullable: true
451
+ })
452
+ deletedAt!: Date | null;
453
+
454
+ @OneToMany(() => Post, (post) => post.user)
455
+ posts!: Post[];
456
+ }
457
+
458
+ @Entity("Posts")
459
+ export class Post {
460
+ @PrimaryGeneratedColumn()
461
+ id!: number;
462
+
463
+ @Column()
464
+ title!: string;
465
+
466
+ @Column()
467
+ userId!: number;
468
+
469
+ @DeleteDateColumn({
470
+ nullable: true
471
+ })
472
+ deletedAt!: Date | null;
473
+
474
+ @ManyToOne(() => User, (user) => user.posts)
475
+ @JoinColumn({
476
+ name: "userId"
477
+ })
478
+ user!: User;
479
+ }
480
+ ```
481
+
482
+ This definition can be used directly with a TypeORM `DataSource` configured for the Google Sheets driver.
483
+
484
+ ## Limitations
485
+
486
+ Entity definitions use TypeORM metadata, but not every TypeORM feature can be represented by Google Sheets.
487
+
488
+ In particular:
489
+
490
+ - Transactions are not supported.
491
+ - Foreign-key constraints are not enforced by Google Sheets.
492
+ - Database-level cascade operations are not available.
493
+ - Nested relation persistence and full cascade persistence are not fully supported.
494
+ - Lazy relation loading is not supported.
495
+ - Relational database guarantees such as referential integrity are not provided.
496
+
497
+ See [Supported Features](supported-features.md) and [Limitations](limitations.md) for the complete compatibility details.
498
+
499
+ ```
500
+
501
+ Ini sudah saya buat sebagai **`docs/entities.md`** dan sengaja tidak memasukkan detail CRUD terlalu jauh supaya dokumentasinya tidak tumpang tindih dengan `crud.md`.
502
+ ```