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,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.
|