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.
- package/LICENSE +21 -0
- package/README.md +31 -0
- package/bun.lock +300 -0
- package/docs/authentication.md +118 -0
- package/docs/configuration.md +680 -0
- package/docs/contributing.md +177 -0
- package/docs/crud.md +351 -0
- package/docs/custom-client.md +165 -0
- package/docs/development.md +243 -0
- package/docs/entities.md +502 -0
- package/docs/find-options.md +296 -0
- package/docs/google-cloud.md +101 -0
- package/docs/installation.md +51 -0
- package/docs/integration-testing.md +178 -0
- package/docs/limitations.md +223 -0
- package/docs/pagination.md +384 -0
- package/docs/performance.md +171 -0
- package/docs/security.md +153 -0
- package/docs/sorting.md +0 -0
- package/docs/supported-features.md +310 -0
- package/docs/synchronization.md +203 -0
- package/package.json +43 -0
- package/src/client/index.ts +589 -0
- package/src/core/data-source.ts +47 -0
- package/src/core/driver.ts +290 -0
- package/src/core/error.ts +144 -0
- package/src/core/memory.ts +152 -0
- package/src/core/query/interpreter.ts +949 -0
- package/src/core/query/runner.ts +1297 -0
- package/src/core/query/types.ts +155 -0
- package/src/core/schema-builder.ts +64 -0
- package/src/core/types.ts +113 -0
- package/src/core/utils.ts +13 -0
- package/src/index.ts +3 -0
- package/tantan-typeorm-gs.code-workspace +8 -0
- package/test/base/0001-data-source.test.ts +252 -0
- package/test/base/0002-operator.test.ts +616 -0
- package/test/base/0003-select.test.ts +281 -0
- package/test/base/0004-aggregate.test.ts +213 -0
- package/test/base/0005-transcation.test.ts +1566 -0
- package/test/base/0006-relation.test.ts +1611 -0
- package/test/base/0007-logging.test.ts +182 -0
- package/test/base/0008-soft-delete.test.ts +649 -0
- package/test/google-sheets/0001-client.test.ts +1705 -0
- package/test/google-sheets/0002-worksheet-management.test.ts +408 -0
- package/test/google-sheets/0003-data-source.test.ts +1935 -0
- package/test/google-sheets/0004-transactions.test.ts +952 -0
- package/test/google-sheets/0005-operator.test.ts +1124 -0
- package/test/google-sheets/0006-object-criteria.test.ts +283 -0
- package/test/google-sheets/0007-performance.test.ts +532 -0
- package/test/public-api.test.ts +34 -0
- package/tsconfig.build.json +21 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
## Contribution Guide
|
|
2
|
+
|
|
3
|
+
Contributions to `tantan-typeorm-gs` should preserve compatibility with TypeORM while keeping the Google Sheets-specific implementation predictable and testable.
|
|
4
|
+
|
|
5
|
+
### Before Contributing
|
|
6
|
+
|
|
7
|
+
Before making changes:
|
|
8
|
+
|
|
9
|
+
1. Understand the existing architecture.
|
|
10
|
+
2. Check whether the requested behavior is already supported.
|
|
11
|
+
3. Identify the layer responsible for the change.
|
|
12
|
+
4. Review existing tests related to that behavior.
|
|
13
|
+
5. Avoid modifying unrelated code.
|
|
14
|
+
|
|
15
|
+
The project favors small, focused changes over broad refactoring.
|
|
16
|
+
|
|
17
|
+
### Development Workflow
|
|
18
|
+
|
|
19
|
+
A typical contribution workflow is:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
Understand
|
|
23
|
+
↓
|
|
24
|
+
Identify affected layer
|
|
25
|
+
↓
|
|
26
|
+
Add or update focused test
|
|
27
|
+
↓
|
|
28
|
+
Implement change
|
|
29
|
+
↓
|
|
30
|
+
Run focused test
|
|
31
|
+
↓
|
|
32
|
+
Run typecheck
|
|
33
|
+
↓
|
|
34
|
+
Run full test suite
|
|
35
|
+
↓
|
|
36
|
+
Update documentation
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Tests First
|
|
40
|
+
|
|
41
|
+
New behavior should have an appropriate test.
|
|
42
|
+
|
|
43
|
+
Use the existing test layer that matches the behavior being changed.
|
|
44
|
+
|
|
45
|
+
| Change | Preferred test |
|
|
46
|
+
| ------------------------------ | ---------------------- |
|
|
47
|
+
| Repository behavior | Repository test |
|
|
48
|
+
| QueryRunner behavior | QueryRunner test |
|
|
49
|
+
| Query interpretation | Query/interpreter test |
|
|
50
|
+
| Google Sheets API behavior | API integration test |
|
|
51
|
+
| Performance-sensitive behavior | Performance test |
|
|
52
|
+
|
|
53
|
+
Do not add duplicate tests when an existing test already covers the same behavior.
|
|
54
|
+
|
|
55
|
+
### Preserve Existing Behavior
|
|
56
|
+
|
|
57
|
+
The existing test suite represents supported behavior and compatibility expectations.
|
|
58
|
+
|
|
59
|
+
A contribution should not modify an existing base test merely to make a new implementation pass.
|
|
60
|
+
|
|
61
|
+
If a new feature causes an existing test to fail:
|
|
62
|
+
|
|
63
|
+
1. Determine whether the existing behavior is actually incorrect.
|
|
64
|
+
2. Determine whether the new implementation introduced a regression.
|
|
65
|
+
3. Fix the implementation when the regression is real.
|
|
66
|
+
4. Change an existing test only when the intended public behavior has genuinely changed.
|
|
67
|
+
|
|
68
|
+
### Google API Tests
|
|
69
|
+
|
|
70
|
+
Tests that communicate with the real Google Sheets API should remain separate from normal driver tests.
|
|
71
|
+
|
|
72
|
+
Real API tests require appropriate Google credentials and a dedicated test spreadsheet.
|
|
73
|
+
|
|
74
|
+
Do not commit:
|
|
75
|
+
|
|
76
|
+
- service-account private keys
|
|
77
|
+
- credential JSON files
|
|
78
|
+
- spreadsheet secrets
|
|
79
|
+
- environment files containing credentials
|
|
80
|
+
- production spreadsheet identifiers when they are sensitive
|
|
81
|
+
|
|
82
|
+
### Pull Requests
|
|
83
|
+
|
|
84
|
+
A pull request should clearly describe:
|
|
85
|
+
|
|
86
|
+
- what changed
|
|
87
|
+
- why the change is required
|
|
88
|
+
- which layer was modified
|
|
89
|
+
- which tests were added or changed
|
|
90
|
+
- how the change was verified
|
|
91
|
+
- whether documentation was updated
|
|
92
|
+
|
|
93
|
+
Keep pull requests focused.
|
|
94
|
+
|
|
95
|
+
Unrelated formatting changes, refactoring, or dependency changes should not be mixed into a feature or bug-fix contribution unless they are necessary.
|
|
96
|
+
|
|
97
|
+
### Verification
|
|
98
|
+
|
|
99
|
+
Before submitting a contribution, run:
|
|
100
|
+
|
|
101
|
+
```bash id="8k4m2p"
|
|
102
|
+
bun run typecheck
|
|
103
|
+
bun test
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
When relevant, also run the specific integration or performance test:
|
|
107
|
+
|
|
108
|
+
```bash id="5v9n1x"
|
|
109
|
+
bun test test/google-sheets/0001-client.test.ts
|
|
110
|
+
bun test test/google-sheets/0007-performance.test.ts
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
All relevant tests should pass before the contribution is submitted.
|
|
114
|
+
|
|
115
|
+
### Documentation
|
|
116
|
+
|
|
117
|
+
If a contribution changes supported behavior, update the appropriate documentation.
|
|
118
|
+
|
|
119
|
+
Examples include:
|
|
120
|
+
|
|
121
|
+
- adding a new supported TypeORM feature
|
|
122
|
+
- changing configuration behavior
|
|
123
|
+
- adding a new limitation
|
|
124
|
+
- changing authentication requirements
|
|
125
|
+
- changing performance characteristics
|
|
126
|
+
- adding or removing Google Sheets API capabilities
|
|
127
|
+
|
|
128
|
+
Documentation should describe behavior that is actually implemented and tested.
|
|
129
|
+
|
|
130
|
+
### Unsupported Features
|
|
131
|
+
|
|
132
|
+
Do not silently emulate unsupported relational-database behavior.
|
|
133
|
+
|
|
134
|
+
For example, Google Sheets does not provide database transactions or database-level foreign-key enforcement.
|
|
135
|
+
|
|
136
|
+
If a TypeORM feature cannot be meaningfully supported by Google Sheets, it should either remain unsupported or be explicitly documented as a driver limitation.
|
|
137
|
+
|
|
138
|
+
### Code Quality
|
|
139
|
+
|
|
140
|
+
Contributions should:
|
|
141
|
+
|
|
142
|
+
- use TypeScript consistently
|
|
143
|
+
- preserve the existing ESM module structure
|
|
144
|
+
- follow the existing naming conventions
|
|
145
|
+
- avoid unnecessary abstractions
|
|
146
|
+
- keep error handling explicit
|
|
147
|
+
- avoid unnecessary production changes
|
|
148
|
+
- maintain the separation between the driver and Google Sheets API client
|
|
149
|
+
|
|
150
|
+
### Contribution Checklist
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
[ ] The affected layer has been identified
|
|
154
|
+
[ ] Existing behavior and tests have been reviewed
|
|
155
|
+
[ ] Focused tests cover the change
|
|
156
|
+
[ ] No unrelated tests were modified
|
|
157
|
+
[ ] TypeScript typecheck passes
|
|
158
|
+
[ ] Full test suite passes
|
|
159
|
+
[ ] Integration tests pass when applicable
|
|
160
|
+
[ ] Performance impact has been considered when applicable
|
|
161
|
+
[ ] Documentation has been updated when necessary
|
|
162
|
+
[ ] No credentials or sensitive data are included
|
|
163
|
+
[ ] The contribution is focused and minimal
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Summary
|
|
167
|
+
|
|
168
|
+
The goal of contributions is not simply to make a feature work, but to maintain a stable TypeORM-compatible driver with predictable Google Sheets behavior.
|
|
169
|
+
|
|
170
|
+
Contributors should prioritize:
|
|
171
|
+
|
|
172
|
+
- compatibility
|
|
173
|
+
- tested behavior
|
|
174
|
+
- minimal changes
|
|
175
|
+
- clear separation of concerns
|
|
176
|
+
- explicit limitations
|
|
177
|
+
- maintainable documentation
|
package/docs/crud.md
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
## CRUD Examples
|
|
2
|
+
|
|
3
|
+
`tantan-typeorm-gs` exposes the standard TypeORM repository API for supported CRUD operations.
|
|
4
|
+
|
|
5
|
+
The examples below assume the following entity:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
|
9
|
+
|
|
10
|
+
@Entity("users")
|
|
11
|
+
class User {
|
|
12
|
+
@PrimaryGeneratedColumn()
|
|
13
|
+
id!: number;
|
|
14
|
+
|
|
15
|
+
@Column()
|
|
16
|
+
name!: string;
|
|
17
|
+
|
|
18
|
+
@Column()
|
|
19
|
+
email!: string;
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
After initializing the data source:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
await dataSource.initialize();
|
|
27
|
+
|
|
28
|
+
const repository = dataSource.getRepository(User);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Create
|
|
32
|
+
|
|
33
|
+
#### Insert One Row
|
|
34
|
+
|
|
35
|
+
Use `repository.insert()` to insert a new row:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const result = await repository.insert({
|
|
39
|
+
name: "Budi",
|
|
40
|
+
email: "budi@example.com"
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For an entity with an auto-generated primary key, the driver generates the ID automatically.
|
|
45
|
+
|
|
46
|
+
#### Insert Multiple Rows
|
|
47
|
+
|
|
48
|
+
Multiple rows can be inserted in a single repository operation:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
await repository.insert([
|
|
52
|
+
{
|
|
53
|
+
name: "Budi",
|
|
54
|
+
email: "budi@example.com"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "Siti",
|
|
58
|
+
email: "siti@example.com"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "Andi",
|
|
62
|
+
email: "andi@example.com"
|
|
63
|
+
}
|
|
64
|
+
]);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The driver processes the rows as a batch operation.
|
|
68
|
+
|
|
69
|
+
#### Save
|
|
70
|
+
|
|
71
|
+
`repository.save()` can also be used:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const user = repository.create({
|
|
75
|
+
name: "Budi",
|
|
76
|
+
email: "budi@example.com"
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await repository.save(user);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`save()` follows TypeORM's repository semantics for determining whether the entity should be inserted or updated.
|
|
83
|
+
|
|
84
|
+
### Read
|
|
85
|
+
|
|
86
|
+
#### Find All Rows
|
|
87
|
+
|
|
88
|
+
Use `repository.find()` to retrieve all rows:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const users = await repository.find();
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The result is an array of entity instances:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
for (const user of users) {
|
|
98
|
+
console.log(user.id, user.name, user.email);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### Find One Row
|
|
103
|
+
|
|
104
|
+
A specific row can be retrieved using `findOne()`:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const user = await repository.findOne({
|
|
108
|
+
where: {
|
|
109
|
+
id: 1
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If no matching row exists, the result is `null`.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
if (!user) {
|
|
118
|
+
console.log("User not found.");
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Find by Conditions
|
|
123
|
+
|
|
124
|
+
Multiple conditions can be supplied:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const users = await repository.find({
|
|
128
|
+
where: {
|
|
129
|
+
name: "Budi"
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The available filtering capabilities are covered in the **Find Options** section.
|
|
135
|
+
|
|
136
|
+
### Update
|
|
137
|
+
|
|
138
|
+
Use `repository.update()` to update rows matching a condition:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
await repository.update(
|
|
142
|
+
{
|
|
143
|
+
id: 1
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "Budi Updated"
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The first argument specifies which rows should be updated.
|
|
152
|
+
|
|
153
|
+
The second argument contains the values to update.
|
|
154
|
+
|
|
155
|
+
Multiple rows can be affected when the condition matches multiple records.
|
|
156
|
+
|
|
157
|
+
### Delete
|
|
158
|
+
|
|
159
|
+
Use `repository.delete()` to delete rows matching a condition:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
await repository.delete({
|
|
163
|
+
id: 1
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The operation removes the matching row from the worksheet.
|
|
168
|
+
|
|
169
|
+
A condition should be supplied when deleting data to avoid unintentionally deleting more rows than intended.
|
|
170
|
+
|
|
171
|
+
### Remove Entity
|
|
172
|
+
|
|
173
|
+
An entity instance can also be removed using `repository.remove()`:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const user = await repository.findOne({
|
|
177
|
+
where: {
|
|
178
|
+
id: 1
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (user) {
|
|
183
|
+
await repository.remove(user);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`remove()` operates on an entity instance, while `delete()` operates directly from a deletion condition.
|
|
188
|
+
|
|
189
|
+
### Update Using an Entity
|
|
190
|
+
|
|
191
|
+
An existing entity can be modified and persisted using `save()`:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
const user = await repository.findOne({
|
|
195
|
+
where: {
|
|
196
|
+
id: 1
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (user) {
|
|
201
|
+
user.name = "Budi Updated";
|
|
202
|
+
|
|
203
|
+
await repository.save(user);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
This is useful when the application already has the entity instance and wants TypeORM to persist its changes.
|
|
208
|
+
|
|
209
|
+
### Generated IDs
|
|
210
|
+
|
|
211
|
+
For an entity using:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
@PrimaryGeneratedColumn()
|
|
215
|
+
id!: number;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
the ID does not need to be supplied when creating a new row:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
await repository.insert({
|
|
222
|
+
name: "Budi",
|
|
223
|
+
email: "budi@example.com"
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The driver assigns the generated primary key.
|
|
228
|
+
|
|
229
|
+
An explicitly supplied generated ID is also preserved:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
await repository.insert({
|
|
233
|
+
id: 100,
|
|
234
|
+
name: "Budi",
|
|
235
|
+
email: "budi@example.com"
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
If the supplied primary key already exists, the driver rejects the operation as a duplicate primary-key insert.
|
|
240
|
+
|
|
241
|
+
### Manually Assigned Primary Keys
|
|
242
|
+
|
|
243
|
+
For an entity using:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
@PrimaryColumn()
|
|
247
|
+
id!: number;
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
the primary key must be supplied:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
await repository.insert({
|
|
254
|
+
id: 1,
|
|
255
|
+
name: "Budi",
|
|
256
|
+
email: "budi@example.com"
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
An insert without the required primary key is rejected.
|
|
261
|
+
|
|
262
|
+
### Lifecycle Hooks
|
|
263
|
+
|
|
264
|
+
CRUD operations participate in supported TypeORM entity lifecycle events.
|
|
265
|
+
|
|
266
|
+
For example:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
@BeforeInsert()
|
|
270
|
+
beforeInsert()
|
|
271
|
+
{
|
|
272
|
+
this.name =
|
|
273
|
+
this.name.toUpperCase();
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
When the entity is inserted, the lifecycle hook is executed as part of the TypeORM operation.
|
|
278
|
+
|
|
279
|
+
Subscribers are also supported for the corresponding lifecycle events.
|
|
280
|
+
|
|
281
|
+
See the **Supported Features** section for the supported lifecycle behavior.
|
|
282
|
+
|
|
283
|
+
### Complete Example
|
|
284
|
+
|
|
285
|
+
A simple application can perform a complete CRUD flow:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
const dataSource = createGoogleSheetsDataSource({
|
|
289
|
+
type: "google-sheets",
|
|
290
|
+
|
|
291
|
+
spreadsheetId: process.env.GOOGLE_SHEETS_SPREADSHEET_ID!,
|
|
292
|
+
|
|
293
|
+
credentials: {
|
|
294
|
+
clientEmail: process.env.GOOGLE_SHEETS_CLIENT_EMAIL!,
|
|
295
|
+
|
|
296
|
+
privateKey: process.env.GOOGLE_SHEETS_PRIVATE_KEY!
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
entities: [User]
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
await dataSource.initialize();
|
|
303
|
+
|
|
304
|
+
const repository = dataSource.getRepository(User);
|
|
305
|
+
|
|
306
|
+
// Create
|
|
307
|
+
const user = await repository.save({
|
|
308
|
+
name: "Budi",
|
|
309
|
+
email: "budi@example.com"
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Read
|
|
313
|
+
const found = await repository.findOne({
|
|
314
|
+
where: {
|
|
315
|
+
id: user.id
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Update
|
|
320
|
+
await repository.update(
|
|
321
|
+
{
|
|
322
|
+
id: user.id
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: "Budi Updated"
|
|
326
|
+
}
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
// Delete
|
|
330
|
+
await repository.delete({
|
|
331
|
+
id: user.id
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
await dataSource.destroy();
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### CRUD Mapping
|
|
338
|
+
|
|
339
|
+
The common repository operations can be summarized as:
|
|
340
|
+
|
|
341
|
+
| Operation | Repository API |
|
|
342
|
+
| ------------------- | --------------------- |
|
|
343
|
+
| Create one | `insert()` / `save()` |
|
|
344
|
+
| Create multiple | `insert()` |
|
|
345
|
+
| Read multiple | `find()` |
|
|
346
|
+
| Read one | `findOne()` |
|
|
347
|
+
| Update | `update()` / `save()` |
|
|
348
|
+
| Delete by condition | `delete()` |
|
|
349
|
+
| Delete entity | `remove()` |
|
|
350
|
+
|
|
351
|
+
These APIs operate through the Google Sheets driver and are subject to the driver's supported TypeORM behavior and Google Sheets limitations.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
## Custom Client
|
|
2
|
+
|
|
3
|
+
The Google Sheets driver allows applications to provide a custom `GoogleSheetsClient`.
|
|
4
|
+
|
|
5
|
+
This is useful when the application needs to control how Google Sheets data is accessed, for example for testing, mocking, caching, or implementing a custom integration.
|
|
6
|
+
|
|
7
|
+
### Default Client
|
|
8
|
+
|
|
9
|
+
If no custom client is provided, the data source creates its own `GoogleSheetsApiClient` using the configured spreadsheet ID and credentials.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const dataSource = createGoogleSheetsDataSource({
|
|
13
|
+
type: "google-sheets",
|
|
14
|
+
|
|
15
|
+
spreadsheetId: process.env.GOOGLE_SHEETS_SPREADSHEET_ID!,
|
|
16
|
+
|
|
17
|
+
credentials: {
|
|
18
|
+
clientEmail: process.env.GOOGLE_SHEETS_CLIENT_EMAIL!,
|
|
19
|
+
|
|
20
|
+
privateKey: process.env.GOOGLE_SHEETS_PRIVATE_KEY!
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
entities: [User]
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The default flow is:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
createGoogleSheetsDataSource()
|
|
31
|
+
↓
|
|
32
|
+
client not provided
|
|
33
|
+
↓
|
|
34
|
+
GoogleSheetsApiClient
|
|
35
|
+
↓
|
|
36
|
+
Google Sheets API
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Providing a Custom Client
|
|
40
|
+
|
|
41
|
+
A custom client can be supplied through the `client` option:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const client = new MyGoogleSheetsClient();
|
|
45
|
+
|
|
46
|
+
const dataSource = createGoogleSheetsDataSource({
|
|
47
|
+
type: "google-sheets",
|
|
48
|
+
|
|
49
|
+
spreadsheetId: process.env.GOOGLE_SHEETS_SPREADSHEET_ID!,
|
|
50
|
+
|
|
51
|
+
credentials: {
|
|
52
|
+
clientEmail: process.env.GOOGLE_SHEETS_CLIENT_EMAIL!,
|
|
53
|
+
|
|
54
|
+
privateKey: process.env.GOOGLE_SHEETS_PRIVATE_KEY!
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
client,
|
|
58
|
+
|
|
59
|
+
entities: [User]
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
When `client` is provided, the driver uses that client instead of creating a default `GoogleSheetsApiClient`.
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
createGoogleSheetsDataSource()
|
|
67
|
+
↓
|
|
68
|
+
client provided?
|
|
69
|
+
┌────┴────┐
|
|
70
|
+
yes no
|
|
71
|
+
↓ ↓
|
|
72
|
+
custom GoogleSheetsApiClient
|
|
73
|
+
client
|
|
74
|
+
↓ ↓
|
|
75
|
+
└────┬────┘
|
|
76
|
+
↓
|
|
77
|
+
GoogleSheetsDriver
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Custom Client Interface
|
|
81
|
+
|
|
82
|
+
A custom client must implement the `GoogleSheetsClient` interface expected by the driver.
|
|
83
|
+
|
|
84
|
+
For example:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import type { GoogleSheetsClient } from "tantan-typeorm-gs";
|
|
88
|
+
|
|
89
|
+
class MyGoogleSheetsClient implements GoogleSheetsClient {
|
|
90
|
+
// Implement the required client methods.
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The exact methods depend on the operations used by the driver.
|
|
95
|
+
|
|
96
|
+
A custom client therefore allows the driver layer to remain independent from the actual Google Sheets transport implementation.
|
|
97
|
+
|
|
98
|
+
### Testing with a Fake Client
|
|
99
|
+
|
|
100
|
+
A custom client is particularly useful for tests.
|
|
101
|
+
|
|
102
|
+
For example:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const client = new Memory({
|
|
106
|
+
users: [
|
|
107
|
+
{
|
|
108
|
+
id: 1,
|
|
109
|
+
name: "Budi"
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const dataSource = createGoogleSheetsDataSource({
|
|
115
|
+
type: "google-sheets",
|
|
116
|
+
|
|
117
|
+
client,
|
|
118
|
+
|
|
119
|
+
entities: [User]
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await dataSource.initialize();
|
|
123
|
+
|
|
124
|
+
const repository = dataSource.getRepository(User);
|
|
125
|
+
|
|
126
|
+
const users = await repository.find();
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
This allows repository and driver behavior to be tested without making requests to the Google Sheets API.
|
|
130
|
+
|
|
131
|
+
### Custom Client Precedence
|
|
132
|
+
|
|
133
|
+
When `client` is supplied:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
client;
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
takes precedence over the default API client.
|
|
140
|
+
|
|
141
|
+
The credentials and spreadsheet ID are still part of the data source configuration, but the custom client is the client instance used by the driver.
|
|
142
|
+
|
|
143
|
+
### When to Use a Custom Client
|
|
144
|
+
|
|
145
|
+
A custom client is useful for:
|
|
146
|
+
|
|
147
|
+
- unit and integration tests
|
|
148
|
+
- fake or in-memory spreadsheet data
|
|
149
|
+
- custom authentication handling
|
|
150
|
+
- request logging
|
|
151
|
+
- caching
|
|
152
|
+
- retry policies
|
|
153
|
+
- alternative Google Sheets transport implementations
|
|
154
|
+
|
|
155
|
+
For normal production usage, the default `GoogleSheetsApiClient` is sufficient when direct Google Sheets API access is required.
|
|
156
|
+
|
|
157
|
+
### Summary
|
|
158
|
+
|
|
159
|
+
| Configuration | Client used |
|
|
160
|
+
| -------------------------------- | ---------------------------------------------- |
|
|
161
|
+
| `client` omitted | `GoogleSheetsApiClient` |
|
|
162
|
+
| `client` provided | Provided `GoogleSheetsClient` |
|
|
163
|
+
| `client` provided with fake data | Useful for testing without Google API requests |
|
|
164
|
+
|
|
165
|
+
The custom client is injected at data source creation time and is used by the `GoogleSheetsDriver`.
|