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,223 @@
1
+ ## Limitations
2
+
3
+ The Google Sheets driver provides a TypeORM-compatible interface, but Google Sheets is not a relational database.
4
+
5
+ As a result, some TypeORM features cannot provide the same guarantees as a traditional database such as PostgreSQL or MySQL.
6
+
7
+ ### No Transactional Guarantees
8
+
9
+ Transactions are not supported.
10
+
11
+ Operations involving multiple writes cannot be treated as one atomic database transaction.
12
+
13
+ For example:
14
+
15
+ ```ts
16
+ await repository.save(user);
17
+ await repository.save(profile);
18
+ ```
19
+
20
+ If the second operation fails, the first operation is not automatically rolled back.
21
+
22
+ Applications that require atomic multi-step updates should use a transactional database instead.
23
+
24
+ ### No Foreign Key Enforcement
25
+
26
+ Relations can be defined and loaded:
27
+
28
+ ```ts
29
+ @ManyToOne(
30
+ () => User,
31
+ {
32
+ nullable: false,
33
+ },
34
+ )
35
+ author!: User;
36
+ ```
37
+
38
+ However, Google Sheets does not enforce foreign-key constraints.
39
+
40
+ The driver therefore cannot guarantee that a relation always points to an existing row.
41
+
42
+ For example, a row may contain:
43
+
44
+ ```text
45
+ authorId = 999
46
+ ```
47
+
48
+ even when no corresponding user exists.
49
+
50
+ Applications are responsible for maintaining referential integrity.
51
+
52
+ ### No Database-Level Cascades
53
+
54
+ Database-level cascading behavior is not available.
55
+
56
+ Operations such as deleting a parent row do not automatically provide the same cascading guarantees as a relational database.
57
+
58
+ If related rows must also be modified or deleted, the application must explicitly perform those operations.
59
+
60
+ ### Limited Relation Persistence
61
+
62
+ Relation metadata and relation loading are supported, but relation persistence does not provide the full behavior of a relational database or TypeORM cascade system.
63
+
64
+ For example, explicitly assigning a foreign-key column is supported:
65
+
66
+ ```ts
67
+ post.userId = user.id;
68
+
69
+ await postRepository.save(post);
70
+ ```
71
+
72
+ However, nested relation persistence and cascade operations are not fully supported.
73
+
74
+ For example, applications should not rely on behavior such as:
75
+
76
+ ```ts
77
+ await userRepository.save({
78
+ name: 'Budi',
79
+ posts: [
80
+ {
81
+ title: 'Post 1',
82
+ },
83
+ ],
84
+ });
85
+ ```
86
+
87
+ when this requires TypeORM to automatically persist related entities.
88
+
89
+ Applications should explicitly persist related entities when necessary.
90
+
91
+ ### Spreadsheet-Based Performance
92
+
93
+ Google Sheets is designed as a spreadsheet and API-based data store, not as a high-performance database engine.
94
+
95
+ Operations over large datasets can become increasingly expensive because rows must be retrieved and processed through the driver.
96
+
97
+ For example, operations such as:
98
+
99
+ ```ts
100
+ await repository.find();
101
+ ```
102
+
103
+ may require processing a large number of worksheet rows.
104
+
105
+ Applications should therefore avoid treating a large spreadsheet as a replacement for a database.
106
+
107
+ ### Offset-Based Pagination
108
+
109
+ Pagination uses `skip` and `take`.
110
+
111
+ ```ts
112
+ await repository.find({
113
+ skip: 100,
114
+ take: 20
115
+ });
116
+ ```
117
+
118
+ This provides offset-based pagination rather than database-style indexed or cursor-based pagination.
119
+
120
+ For large worksheets, deep offsets may require processing a substantial amount of data before the requested page can be returned.
121
+
122
+ ### Limited Query Semantics
123
+
124
+ The driver supports the query operations implemented by its query interpreter and query runner.
125
+
126
+ It does not provide every SQL feature available in a relational database.
127
+
128
+ Applications should not assume that arbitrary SQL syntax supported by PostgreSQL, MySQL, or another database will work with Google Sheets.
129
+
130
+ Unsupported operations are rejected rather than silently interpreted as equivalent operations.
131
+
132
+ ### Concurrent Updates
133
+
134
+ Google Sheets does not provide the same concurrency guarantees as a transactional database.
135
+
136
+ When multiple applications or users modify the same worksheet concurrently, applications should consider the possibility of conflicting updates.
137
+
138
+ The driver should therefore not be used for workloads that require strong database-level concurrency control.
139
+
140
+ ### Primary Key Limitations
141
+
142
+ Generated primary keys are supported for the strategies implemented by the driver.
143
+
144
+ Currently:
145
+
146
+ | Generation strategy | Status |
147
+ | ------------------- | ------------- |
148
+ | `increment` | Supported |
149
+ | `uuid` | Supported |
150
+ | `identity` | Not supported |
151
+ | `rowid` | Not supported |
152
+
153
+ For manually defined primary keys using `@PrimaryColumn()`, the application must provide a value.
154
+
155
+ Duplicate generated primary keys are rejected by the driver.
156
+
157
+ ### Schema Management Limitations
158
+
159
+ Synchronization operates on worksheet structures rather than a relational database schema.
160
+
161
+ There are no traditional database objects such as:
162
+
163
+ * indexes with database query-planning semantics
164
+ * foreign-key constraints
165
+ * transactional schema changes
166
+ * database-enforced unique constraints
167
+
168
+ `dropSchema` and `synchronize` should therefore be used carefully, particularly when the spreadsheet contains data that must be preserved.
169
+
170
+ ### Relations Are Not Relational Database Guarantees
171
+
172
+ Relation metadata and relation loading are supported, but this should not be interpreted as full relational-database support.
173
+
174
+ The following remain application-level responsibilities:
175
+
176
+ * referential integrity
177
+ * cascade behavior
178
+ * transactional relation updates
179
+ * consistency across related worksheets
180
+
181
+ ### API Dependency
182
+
183
+ The default client communicates with Google Sheets through the Google Sheets API.
184
+
185
+ Therefore, applications using the default client depend on:
186
+
187
+ * Google Cloud configuration
188
+ * valid authentication credentials
189
+ * spreadsheet permissions
190
+ * Google API availability
191
+ * API quotas and limits
192
+
193
+ A custom client can be used when the application needs different transport or testing behavior.
194
+
195
+ ### When Not to Use This Driver
196
+
197
+ A relational database is generally more appropriate when the application requires:
198
+
199
+ * transactions
200
+ * strong consistency guarantees
201
+ * foreign-key enforcement
202
+ * complex relational queries
203
+ * large-scale datasets
204
+ * high-frequency concurrent writes
205
+ * database-level indexing and query optimization
206
+
207
+ The Google Sheets driver is better suited to workloads where spreadsheet accessibility and TypeORM integration are more important than full relational-database capabilities.
208
+
209
+ ### Summary
210
+
211
+ The main limitation is architectural:
212
+
213
+ ```text
214
+ TypeORM API
215
+ ↓
216
+ Google Sheets Driver
217
+ ↓
218
+ Spreadsheet
219
+ ```
220
+
221
+ The TypeORM API provides a familiar programming model, but it cannot add database capabilities that Google Sheets itself does not provide.
222
+
223
+ The driver should therefore be considered a **TypeORM interface for Google Sheets**, not a replacement for a relational database.
@@ -0,0 +1,384 @@
1
+ ## Pagination
2
+
3
+ Pagination can be implemented using TypeORM's `skip` and `take` find options.
4
+
5
+ The Google Sheets driver supports this offset-based pagination pattern.
6
+
7
+ ### Basic Pagination
8
+
9
+ Define the page number and page size:
10
+
11
+ ```ts id="8j3p7k"
12
+ const page = 1;
13
+ const pageSize = 20;
14
+
15
+ const users = await repository.find({
16
+ skip: (page - 1) * pageSize,
17
+ take: pageSize
18
+ });
19
+ ```
20
+
21
+ For example:
22
+
23
+ | Page | Page size | Skip | Take |
24
+ | ---: | --------: | ---: | ---: |
25
+ | 1 | 20 | 0 | 20 |
26
+ | 2 | 20 | 20 | 20 |
27
+ | 3 | 20 | 40 | 20 |
28
+
29
+ The formula is:
30
+
31
+ ```text id="wqk5j1"
32
+ skip = (page - 1) * pageSize
33
+ ```
34
+
35
+ ### Pagination with Sorting
36
+
37
+ Pagination should generally be combined with a deterministic ordering:
38
+
39
+ ```ts id="6xj2pz"
40
+ const page = 2;
41
+ const pageSize = 20;
42
+
43
+ const users = await repository.find({
44
+ order: {
45
+ id: "ASC"
46
+ },
47
+
48
+ skip: (page - 1) * pageSize,
49
+
50
+ take: pageSize
51
+ });
52
+ ```
53
+
54
+ Using `order` makes the result ordering explicit before applying the offset and limit.
55
+
56
+ ### Pagination with Filtering
57
+
58
+ Pagination can also be combined with `where`:
59
+
60
+ ```ts id="h9k4s2"
61
+ const page = 1;
62
+ const pageSize = 20;
63
+
64
+ const users = await repository.find({
65
+ where: {
66
+ name: "Budi"
67
+ },
68
+
69
+ order: {
70
+ id: "ASC"
71
+ },
72
+
73
+ skip: (page - 1) * pageSize,
74
+
75
+ take: pageSize
76
+ });
77
+ ```
78
+
79
+ The filtering is applied before the pagination window is returned.
80
+
81
+ ### Pagination with Total Count
82
+
83
+ When the application needs both the current page and the total number of matching records, use `findAndCount()`:
84
+
85
+ ```ts id="4w8m1r"
86
+ const page = 1;
87
+ const pageSize = 20;
88
+
89
+ const [users, total] = await repository.findAndCount({
90
+ where: {
91
+ name: "Budi"
92
+ },
93
+
94
+ order: {
95
+ id: "ASC"
96
+ },
97
+
98
+ skip: (page - 1) * pageSize,
99
+
100
+ take: pageSize
101
+ });
102
+ ```
103
+
104
+ `users` contains the entities for the requested page, while `total` contains the total number of matching entities.
105
+
106
+ For example:
107
+
108
+ ```ts id="5h2d8q"
109
+ const totalPages = Math.ceil(total / pageSize);
110
+ ```
111
+
112
+ The application can then expose pagination information:
113
+
114
+ ```ts id="p6c1vx"
115
+ {
116
+ data: users,
117
+ page,
118
+ pageSize,
119
+ total,
120
+ totalPages,
121
+ }
122
+ ```
123
+
124
+ ### Reusable Pagination Helper
125
+
126
+ A helper function can be used to keep pagination logic consistent:
127
+
128
+ ```ts id="9m4z7a"
129
+ async function findPage(page: number, pageSize: number) {
130
+ const [data, total] = await repository.findAndCount({
131
+ order: {
132
+ id: "ASC"
133
+ },
134
+
135
+ skip: (page - 1) * pageSize,
136
+
137
+ take: pageSize
138
+ });
139
+
140
+ return {
141
+ data,
142
+ page,
143
+ pageSize,
144
+ total,
145
+ totalPages: Math.ceil(total / pageSize)
146
+ };
147
+ }
148
+ ```
149
+
150
+ Usage:
151
+
152
+ ```ts id="2c8v1n"
153
+ const result = await findPage(2, 20);
154
+
155
+ console.log(result.data);
156
+ console.log(result.total);
157
+ console.log(result.totalPages);
158
+ ```
159
+
160
+ ### Pagination Considerations
161
+
162
+ Pagination through `skip` and `take` is offset-based.
163
+
164
+ For large worksheets, increasing the offset means the driver still needs to process the preceding rows before returning the requested page. Therefore, pagination is convenient for normal application-level data sets but should not be treated as equivalent to database indexing or cursor-based pagination.
165
+
166
+ For large-scale data, consider whether Google Sheets is appropriate as the primary data store and evaluate the performance characteristics of the target spreadsheet.
167
+
168
+ ### Summary
169
+
170
+ The basic pagination pattern is:
171
+
172
+ ```text id="6v3yqk"
173
+ page
174
+ ↓
175
+ (page - 1) × pageSize
176
+ ↓
177
+ skip
178
+ ↓
179
+ take pageSize
180
+ ↓
181
+ current page
182
+ ```
183
+
184
+ For applications that need the total number of records, prefer:
185
+
186
+ ```ts id="8n2w6c"
187
+ repository.findAndCount({
188
+ skip,
189
+ take
190
+ });
191
+ ```
192
+
193
+ This provides both the current page and the total matching record count.
194
+
195
+ ## Sorting
196
+
197
+ Sorting is performed using TypeORM's `order` find option.
198
+
199
+ The Google Sheets driver supports sorting by one or more entity properties.
200
+
201
+ ### Sort Ascending
202
+
203
+ To sort a result in ascending order:
204
+
205
+ ```ts id="6p2r8m"
206
+ const users = await repository.find({
207
+ order: {
208
+ name: "ASC"
209
+ }
210
+ });
211
+ ```
212
+
213
+ For example, names are returned alphabetically:
214
+
215
+ ```text id="5k1d9v"
216
+ Andi
217
+ Budi
218
+ Citra
219
+ Siti
220
+ ```
221
+
222
+ ### Sort Descending
223
+
224
+ Use `DESC` for descending order:
225
+
226
+ ```ts id="1x7m4q"
227
+ const users = await repository.find({
228
+ order: {
229
+ name: "DESC"
230
+ }
231
+ });
232
+ ```
233
+
234
+ The result is returned in reverse ordering.
235
+
236
+ ### Multiple Sort Fields
237
+
238
+ Multiple properties can be specified:
239
+
240
+ ```ts id="8q4n2s"
241
+ const users = await repository.find({
242
+ order: {
243
+ name: "ASC",
244
+ email: "DESC"
245
+ }
246
+ });
247
+ ```
248
+
249
+ The first field is used as the primary sort key.
250
+
251
+ The next field is used when rows have the same value for the preceding field.
252
+
253
+ Conceptually:
254
+
255
+ ```text
256
+ name ASC
257
+ ↓
258
+ email DESC
259
+ ```
260
+
261
+ ### Sorting with Filtering
262
+
263
+ Sorting can be combined with `where`:
264
+
265
+ ```ts id="3c7w9k"
266
+ const users = await repository.find({
267
+ where: {
268
+ name: "Budi"
269
+ },
270
+
271
+ order: {
272
+ email: "ASC"
273
+ }
274
+ });
275
+ ```
276
+
277
+ Only matching rows are returned, ordered by `email`.
278
+
279
+ ### Sorting with Pagination
280
+
281
+ For pagination, sorting should normally be specified explicitly:
282
+
283
+ ```ts id="0v5m2x"
284
+ const page = 2;
285
+ const pageSize = 20;
286
+
287
+ const users = await repository.find({
288
+ order: {
289
+ id: "ASC"
290
+ },
291
+
292
+ skip: (page - 1) * pageSize,
293
+
294
+ take: pageSize
295
+ });
296
+ ```
297
+
298
+ Using a deterministic sort order makes the pagination result predictable.
299
+
300
+ ### Sorting with Find One
301
+
302
+ `findOne()` can also use `order`:
303
+
304
+ ```ts id="7r1k6p"
305
+ const user = await repository.findOne({
306
+ order: {
307
+ id: "ASC"
308
+ }
309
+ });
310
+ ```
311
+
312
+ This is useful when a condition may match multiple rows but the application needs a deterministic first result.
313
+
314
+ ### Entity Property Names
315
+
316
+ The `order` option uses entity property names:
317
+
318
+ ```ts id="2d8x5m"
319
+ @Entity("users")
320
+ class User {
321
+ @PrimaryGeneratedColumn()
322
+ id!: number;
323
+
324
+ @Column()
325
+ displayName!: string;
326
+ }
327
+ ```
328
+
329
+ The repository query uses:
330
+
331
+ ```ts id="9q3h7v"
332
+ await repository.find({
333
+ order: {
334
+ displayName: "ASC"
335
+ }
336
+ });
337
+ ```
338
+
339
+ If a custom database/worksheet column name is configured, the application still works with the entity property:
340
+
341
+ ```ts id="4m6z1c"
342
+ @Column({
343
+ name: 'display_name',
344
+ })
345
+ displayName!: string;
346
+ ```
347
+
348
+ Query:
349
+
350
+ ```ts id="1s8k4w"
351
+ await repository.find({
352
+ order: {
353
+ displayName: "ASC"
354
+ }
355
+ });
356
+ ```
357
+
358
+ TypeORM metadata resolves the entity property to the corresponding worksheet column.
359
+
360
+ ### Summary
361
+
362
+ The basic sorting syntax is:
363
+
364
+ ```ts id="5n2q8b"
365
+ await repository.find({
366
+ order: {
367
+ propertyName: "ASC"
368
+ }
369
+ });
370
+ ```
371
+
372
+ or:
373
+
374
+ ```ts id="7c4m1x"
375
+ await repository.find({
376
+ order: {
377
+ propertyName: "DESC"
378
+ }
379
+ });
380
+ ```
381
+
382
+ Multiple fields can be combined when a secondary ordering is required.
383
+
384
+ Sorting is especially useful together with `skip` and `take` for predictable pagination.