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,155 @@
|
|
|
1
|
+
import type { GoogleSheetsRow } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export interface InsertQuery
|
|
4
|
+
{
|
|
5
|
+
columns: string[];
|
|
6
|
+
values: string[][];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface UpdateQuery
|
|
10
|
+
{
|
|
11
|
+
table: string;
|
|
12
|
+
assignments: {
|
|
13
|
+
column: string;
|
|
14
|
+
value: string;
|
|
15
|
+
}[];
|
|
16
|
+
where: WhereExpression;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DeleteQuery
|
|
20
|
+
{
|
|
21
|
+
table: string;
|
|
22
|
+
where: WhereExpression;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SelectColumn
|
|
26
|
+
{
|
|
27
|
+
tableAlias: string;
|
|
28
|
+
column: string;
|
|
29
|
+
alias: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FromQuery
|
|
33
|
+
{
|
|
34
|
+
table: string;
|
|
35
|
+
alias: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type JoinType =
|
|
39
|
+
| 'LEFT'
|
|
40
|
+
| 'INNER';
|
|
41
|
+
|
|
42
|
+
export type JoinOperand =
|
|
43
|
+
| {
|
|
44
|
+
type: 'column';
|
|
45
|
+
tableAlias: string;
|
|
46
|
+
column: string;
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
type: 'parameter';
|
|
50
|
+
value: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type JoinConditionOperator =
|
|
54
|
+
| '='
|
|
55
|
+
| 'IS NULL'
|
|
56
|
+
| 'IS NOT NULL';
|
|
57
|
+
|
|
58
|
+
export interface JoinCondition
|
|
59
|
+
{
|
|
60
|
+
left: JoinOperand;
|
|
61
|
+
operator: JoinConditionOperator;
|
|
62
|
+
right?: JoinOperand;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface JoinQuery
|
|
66
|
+
{
|
|
67
|
+
type: JoinType;
|
|
68
|
+
table: string;
|
|
69
|
+
alias: string;
|
|
70
|
+
conditions: JoinCondition[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type JoinedRow = Record<string, GoogleSheetsRow | undefined>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Operators generated by TypeORM FindOperator
|
|
77
|
+
* that can be evaluated by the Google Sheets driver.
|
|
78
|
+
*/
|
|
79
|
+
export type WhereOperator =
|
|
80
|
+
| '='
|
|
81
|
+
| '!='
|
|
82
|
+
| '<>'
|
|
83
|
+
| '>'
|
|
84
|
+
| '>='
|
|
85
|
+
| '<'
|
|
86
|
+
| '<='
|
|
87
|
+
| 'IS NULL'
|
|
88
|
+
| 'IS NOT NULL'
|
|
89
|
+
| 'IN'
|
|
90
|
+
| 'NOT IN'
|
|
91
|
+
| 'ANY'
|
|
92
|
+
| 'LIKE'
|
|
93
|
+
| 'NOT LIKE'
|
|
94
|
+
| 'ILIKE'
|
|
95
|
+
| 'NOT ILIKE'
|
|
96
|
+
| 'BETWEEN';
|
|
97
|
+
|
|
98
|
+
export interface WhereCondition
|
|
99
|
+
{
|
|
100
|
+
tableAlias: string;
|
|
101
|
+
column: string;
|
|
102
|
+
operator: WhereOperator;
|
|
103
|
+
parameters: string[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
export type WhereExpression =
|
|
108
|
+
| {
|
|
109
|
+
type: 'condition';
|
|
110
|
+
condition: WhereCondition;
|
|
111
|
+
}
|
|
112
|
+
| {
|
|
113
|
+
type: 'and';
|
|
114
|
+
expressions: WhereExpression[];
|
|
115
|
+
}
|
|
116
|
+
| {
|
|
117
|
+
type: 'or';
|
|
118
|
+
expressions: WhereExpression[];
|
|
119
|
+
}
|
|
120
|
+
| {
|
|
121
|
+
type: 'not';
|
|
122
|
+
expression: WhereExpression;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
export type AggregateFunction =
|
|
127
|
+
| 'COUNT'
|
|
128
|
+
| 'SUM'
|
|
129
|
+
| 'AVG'
|
|
130
|
+
| 'MIN'
|
|
131
|
+
| 'MAX';
|
|
132
|
+
|
|
133
|
+
export interface AggregateColumn
|
|
134
|
+
{
|
|
135
|
+
function: AggregateFunction;
|
|
136
|
+
argument: string;
|
|
137
|
+
alias: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type OrderDirection =
|
|
141
|
+
| 'ASC'
|
|
142
|
+
| 'DESC';
|
|
143
|
+
|
|
144
|
+
export interface OrderBy
|
|
145
|
+
{
|
|
146
|
+
tableAlias: string;
|
|
147
|
+
column: string;
|
|
148
|
+
direction: OrderDirection;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface Pagination
|
|
152
|
+
{
|
|
153
|
+
limit?: number;
|
|
154
|
+
offset?: number;
|
|
155
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { DataSource } from "typeorm";
|
|
2
|
+
import type { SqlInMemory } from "typeorm/driver/SqlInMemory.js";
|
|
3
|
+
import type { SchemaBuilder } from "typeorm/schema-builder/SchemaBuilder.js";
|
|
4
|
+
import type { GoogleSheetsDriver } from "./driver.js";
|
|
5
|
+
|
|
6
|
+
export class GoogleSheetsSchemaBuilder implements SchemaBuilder
|
|
7
|
+
{
|
|
8
|
+
constructor(
|
|
9
|
+
private readonly connection: DataSource,
|
|
10
|
+
private readonly driver: GoogleSheetsDriver
|
|
11
|
+
) { }
|
|
12
|
+
|
|
13
|
+
async build(): Promise<void>
|
|
14
|
+
{
|
|
15
|
+
const metadatas = this.connection.entityMetadatas;
|
|
16
|
+
|
|
17
|
+
for (const metadata of metadatas)
|
|
18
|
+
{
|
|
19
|
+
if (!metadata.synchronize) continue;
|
|
20
|
+
|
|
21
|
+
const sheetName =
|
|
22
|
+
this.driver.buildTableName(
|
|
23
|
+
metadata.tableName,
|
|
24
|
+
metadata.schema,
|
|
25
|
+
metadata.database,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const exists = await this.driver.client.hasSheet(sheetName);
|
|
29
|
+
|
|
30
|
+
if (!exists)
|
|
31
|
+
{
|
|
32
|
+
await this.driver.client.createSheet(sheetName);
|
|
33
|
+
|
|
34
|
+
this.connection.logger.logSchemaBuild(`Created sheet "${sheetName}"`);
|
|
35
|
+
|
|
36
|
+
const headers = metadata.columns.map((column) => column.databaseName);
|
|
37
|
+
|
|
38
|
+
await this.driver.client.insertHeaders(sheetName, headers);
|
|
39
|
+
|
|
40
|
+
this.connection.logger.logSchemaBuild(`Created columns for "${sheetName}": ${headers.join(', ')}`);
|
|
41
|
+
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const existingHeaders = await this.driver.client.getHeaders(sheetName);
|
|
46
|
+
|
|
47
|
+
const metadataHeaders = metadata.columns.map((column) => column.databaseName);
|
|
48
|
+
|
|
49
|
+
const missingHeaders = metadataHeaders.filter((header) => !existingHeaders.includes(header));
|
|
50
|
+
|
|
51
|
+
if (missingHeaders.length === 0) continue;
|
|
52
|
+
|
|
53
|
+
await this.driver.client.insertHeaders(sheetName, [...existingHeaders, ...missingHeaders]);
|
|
54
|
+
|
|
55
|
+
this.connection.logger.logSchemaBuild(`Added columns to "${sheetName}": ${missingHeaders.join(', ')}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
log(): Promise<SqlInMemory>
|
|
60
|
+
{
|
|
61
|
+
throw new Error("Method not implemented.");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { DataSourceOptions } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export interface GoogleSheetsDataSourceOptionsBase
|
|
4
|
+
{
|
|
5
|
+
type: 'google-sheets';
|
|
6
|
+
|
|
7
|
+
entities?: DataSourceOptions['entities'];
|
|
8
|
+
subscribers?: DataSourceOptions['subscribers'];
|
|
9
|
+
migrations?: DataSourceOptions['migrations'];
|
|
10
|
+
|
|
11
|
+
synchronize?: boolean;
|
|
12
|
+
migrationsRun?: boolean;
|
|
13
|
+
dropSchema?: boolean;
|
|
14
|
+
|
|
15
|
+
logging?: DataSourceOptions['logging'];
|
|
16
|
+
logger?: DataSourceOptions['logger'];
|
|
17
|
+
|
|
18
|
+
name?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface GoogleSheetsCredentials
|
|
22
|
+
{
|
|
23
|
+
clientEmail: string;
|
|
24
|
+
privateKey: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface GoogleSheetsConsumeOptions
|
|
28
|
+
{
|
|
29
|
+
spreadsheetId: string;
|
|
30
|
+
credentials: GoogleSheetsCredentials;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GoogleSheetsDataSourceWithClientOptions extends GoogleSheetsDataSourceOptionsBase
|
|
34
|
+
{
|
|
35
|
+
client: GoogleSheetsClient;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface GoogleSheetsDataSourceWithCredentialsOptions extends GoogleSheetsDataSourceOptionsBase
|
|
39
|
+
{
|
|
40
|
+
spreadsheetId: string;
|
|
41
|
+
credentials: GoogleSheetsCredentials;
|
|
42
|
+
|
|
43
|
+
client?: GoogleSheetsClient;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type GoogleSheetsDataSourceOptions =
|
|
47
|
+
| GoogleSheetsDataSourceWithClientOptions
|
|
48
|
+
| GoogleSheetsDataSourceWithCredentialsOptions;
|
|
49
|
+
|
|
50
|
+
export interface GoogleSheetsClient
|
|
51
|
+
{
|
|
52
|
+
connect(): Promise<void>;
|
|
53
|
+
|
|
54
|
+
disconnect(): Promise<void>;
|
|
55
|
+
|
|
56
|
+
hasSheet(
|
|
57
|
+
sheetName: string,
|
|
58
|
+
): Promise<boolean>;
|
|
59
|
+
|
|
60
|
+
createSheet(sheetName: string): Promise<void>;
|
|
61
|
+
|
|
62
|
+
getSheetMetadata(
|
|
63
|
+
sheetName: string,
|
|
64
|
+
): Promise<GoogleSheetsSheetMetadata | null>;
|
|
65
|
+
|
|
66
|
+
getHeaders(
|
|
67
|
+
sheetName: string,
|
|
68
|
+
): Promise<string[]>;
|
|
69
|
+
|
|
70
|
+
insertHeaders(
|
|
71
|
+
sheetName: string,
|
|
72
|
+
headers: string[],
|
|
73
|
+
): Promise<void>;
|
|
74
|
+
|
|
75
|
+
renameSheet(
|
|
76
|
+
oldSheetName: string,
|
|
77
|
+
newSheetName: string,
|
|
78
|
+
): Promise<void>;
|
|
79
|
+
|
|
80
|
+
deleteSheet(
|
|
81
|
+
sheetName: string,
|
|
82
|
+
): Promise<void>;
|
|
83
|
+
|
|
84
|
+
getRows(
|
|
85
|
+
sheetName: string,
|
|
86
|
+
): Promise<GoogleSheetsRow[]>;
|
|
87
|
+
|
|
88
|
+
insertRows(
|
|
89
|
+
sheetName: string,
|
|
90
|
+
rows: GoogleSheetsRow[],
|
|
91
|
+
): Promise<void>;
|
|
92
|
+
|
|
93
|
+
updateRows(
|
|
94
|
+
sheetName: string,
|
|
95
|
+
predicate: (row: GoogleSheetsRow) => boolean,
|
|
96
|
+
update: Partial<GoogleSheetsRow>,
|
|
97
|
+
): Promise<number>;
|
|
98
|
+
|
|
99
|
+
deleteRows(
|
|
100
|
+
sheetName: string,
|
|
101
|
+
predicate: (row: GoogleSheetsRow) => boolean,
|
|
102
|
+
): Promise<number>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type GoogleSheetsRow = Record<string, unknown>;
|
|
106
|
+
|
|
107
|
+
export interface GoogleSheetsSheetMetadata
|
|
108
|
+
{
|
|
109
|
+
sheetId: number;
|
|
110
|
+
title: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const DEFAULT_MAX_RETRIES = 3;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function validateWorksheetName(sheetName: string): void
|
|
2
|
+
{
|
|
3
|
+
if (sheetName.length === 0) throw new Error('Worksheet name cannot be empty.');
|
|
4
|
+
if (sheetName.length > 100) throw new Error('Worksheet name cannot exceed 100 characters.');
|
|
5
|
+
if (/[:\\/?*\[\]]/.test(sheetName)) throw new Error('Worksheet name contains invalid characters.');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function buildWorksheetRange(sheetName: string, range: string): string
|
|
9
|
+
{
|
|
10
|
+
const escapedSheetName = `'${sheetName.replace(/'/g, "''")}'`;
|
|
11
|
+
|
|
12
|
+
return `${escapedSheetName}!${range}`;
|
|
13
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
|
3
|
+
import { Memory } from '../../src/core/memory';
|
|
4
|
+
import { createGoogleSheetsDataSource } from '../../src/core/data-source';
|
|
5
|
+
|
|
6
|
+
describe('GoogleSheetsDataSource', () =>
|
|
7
|
+
{
|
|
8
|
+
@Entity()
|
|
9
|
+
class User
|
|
10
|
+
{
|
|
11
|
+
@PrimaryGeneratedColumn()
|
|
12
|
+
id!: number;
|
|
13
|
+
|
|
14
|
+
@Column()
|
|
15
|
+
name!: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@Entity()
|
|
19
|
+
class DataTypeUser
|
|
20
|
+
{
|
|
21
|
+
@PrimaryGeneratedColumn()
|
|
22
|
+
id!: number;
|
|
23
|
+
|
|
24
|
+
@Column()
|
|
25
|
+
name!: string;
|
|
26
|
+
|
|
27
|
+
@Column()
|
|
28
|
+
age!: number;
|
|
29
|
+
|
|
30
|
+
@Column()
|
|
31
|
+
active!: boolean;
|
|
32
|
+
|
|
33
|
+
@Column()
|
|
34
|
+
createdAt!: Date;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Entity()
|
|
38
|
+
class TimestampUser
|
|
39
|
+
{
|
|
40
|
+
@PrimaryGeneratedColumn()
|
|
41
|
+
id!: number;
|
|
42
|
+
|
|
43
|
+
@Column()
|
|
44
|
+
name!: string;
|
|
45
|
+
|
|
46
|
+
@CreateDateColumn()
|
|
47
|
+
createdAt!: Date;
|
|
48
|
+
|
|
49
|
+
@UpdateDateColumn()
|
|
50
|
+
updatedAt!: Date;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
test('creates DataSource with GoogleSheetsDriver', () =>
|
|
54
|
+
{
|
|
55
|
+
const dataSource = createGoogleSheetsDataSource({
|
|
56
|
+
type: 'google-sheets',
|
|
57
|
+
client: new Memory(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(dataSource.driver.constructor.name)
|
|
61
|
+
.toBe('GoogleSheetsDriver');
|
|
62
|
+
|
|
63
|
+
expect(dataSource.manager.constructor.name)
|
|
64
|
+
.toBe('EntityManager');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('initializes and builds entity metadata', async () =>
|
|
68
|
+
{
|
|
69
|
+
const dataSource = createGoogleSheetsDataSource({
|
|
70
|
+
type: 'google-sheets',
|
|
71
|
+
client: new Memory(),
|
|
72
|
+
entities: [User],
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await dataSource.initialize();
|
|
76
|
+
|
|
77
|
+
expect(dataSource.isInitialized).toBe(true);
|
|
78
|
+
expect(dataSource.entityMetadatas).toHaveLength(1);
|
|
79
|
+
|
|
80
|
+
const metadata = dataSource.getMetadata(User);
|
|
81
|
+
|
|
82
|
+
expect(metadata.tableName).toBe('user');
|
|
83
|
+
expect(metadata.columns).toHaveLength(2);
|
|
84
|
+
|
|
85
|
+
await dataSource.destroy();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('creates repository', async () =>
|
|
89
|
+
{
|
|
90
|
+
const dataSource = createGoogleSheetsDataSource({
|
|
91
|
+
type: 'google-sheets',
|
|
92
|
+
client: new Memory(),
|
|
93
|
+
entities: [User],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await dataSource.initialize();
|
|
97
|
+
|
|
98
|
+
const repository = dataSource.getRepository(User);
|
|
99
|
+
|
|
100
|
+
expect(repository.metadata).toBe(dataSource.getMetadata(User));
|
|
101
|
+
expect(repository.target).toBe(User);
|
|
102
|
+
|
|
103
|
+
await dataSource.destroy();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('saves supported column data types', async () =>
|
|
107
|
+
{
|
|
108
|
+
const client =
|
|
109
|
+
new Memory();
|
|
110
|
+
|
|
111
|
+
const dataSource =
|
|
112
|
+
createGoogleSheetsDataSource({
|
|
113
|
+
type: 'google-sheets',
|
|
114
|
+
client,
|
|
115
|
+
entities: [
|
|
116
|
+
DataTypeUser,
|
|
117
|
+
]
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await dataSource.initialize();
|
|
121
|
+
|
|
122
|
+
const user =
|
|
123
|
+
dataSource
|
|
124
|
+
.getRepository(DataTypeUser)
|
|
125
|
+
.create({
|
|
126
|
+
name: 'John',
|
|
127
|
+
age: 30,
|
|
128
|
+
active: true,
|
|
129
|
+
createdAt: new Date(),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await dataSource
|
|
133
|
+
.getRepository(DataTypeUser)
|
|
134
|
+
.save(user);
|
|
135
|
+
|
|
136
|
+
const result =
|
|
137
|
+
await dataSource
|
|
138
|
+
.getRepository(DataTypeUser)
|
|
139
|
+
.findOneBy({
|
|
140
|
+
id: user.id,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(result)
|
|
144
|
+
.toBeDefined();
|
|
145
|
+
|
|
146
|
+
expect(result?.name)
|
|
147
|
+
.toBe('John');
|
|
148
|
+
|
|
149
|
+
expect(result?.age)
|
|
150
|
+
.toBe(30);
|
|
151
|
+
|
|
152
|
+
expect(result?.active)
|
|
153
|
+
.toBe(true);
|
|
154
|
+
|
|
155
|
+
expect(result?.createdAt)
|
|
156
|
+
.toEqual(user.createdAt);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('assigns create and update dates when saving a new entity', async () =>
|
|
160
|
+
{
|
|
161
|
+
const client =
|
|
162
|
+
new Memory();
|
|
163
|
+
|
|
164
|
+
const dataSource =
|
|
165
|
+
createGoogleSheetsDataSource({
|
|
166
|
+
type: 'google-sheets',
|
|
167
|
+
client,
|
|
168
|
+
entities: [
|
|
169
|
+
TimestampUser,
|
|
170
|
+
]
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await dataSource.initialize();
|
|
174
|
+
|
|
175
|
+
const user =
|
|
176
|
+
dataSource
|
|
177
|
+
.getRepository(TimestampUser)
|
|
178
|
+
.create({
|
|
179
|
+
name: 'John',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await dataSource
|
|
183
|
+
.getRepository(TimestampUser)
|
|
184
|
+
.save(user);
|
|
185
|
+
|
|
186
|
+
expect(user.createdAt)
|
|
187
|
+
.toBeInstanceOf(Date);
|
|
188
|
+
|
|
189
|
+
expect(user.updatedAt)
|
|
190
|
+
.toBeInstanceOf(Date);
|
|
191
|
+
|
|
192
|
+
expect(user.createdAt.getTime())
|
|
193
|
+
.toBeLessThanOrEqual(
|
|
194
|
+
user.updatedAt.getTime(),
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('updates update date when updating an existing entity', async () =>
|
|
199
|
+
{
|
|
200
|
+
const client =
|
|
201
|
+
new Memory();
|
|
202
|
+
|
|
203
|
+
const dataSource =
|
|
204
|
+
createGoogleSheetsDataSource({
|
|
205
|
+
type: 'google-sheets',
|
|
206
|
+
client,
|
|
207
|
+
entities: [
|
|
208
|
+
TimestampUser,
|
|
209
|
+
]
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await dataSource.initialize();
|
|
213
|
+
|
|
214
|
+
const repository =
|
|
215
|
+
dataSource.getRepository(TimestampUser);
|
|
216
|
+
|
|
217
|
+
const user =
|
|
218
|
+
repository.create({
|
|
219
|
+
name: 'John',
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await repository.save(user);
|
|
223
|
+
|
|
224
|
+
const createdAt =
|
|
225
|
+
user.createdAt.getTime();
|
|
226
|
+
|
|
227
|
+
const updatedAt =
|
|
228
|
+
user.updatedAt.getTime();
|
|
229
|
+
|
|
230
|
+
await new Promise(
|
|
231
|
+
(resolve) =>
|
|
232
|
+
setTimeout(resolve, 5),
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
user.name = 'Jane';
|
|
236
|
+
|
|
237
|
+
await repository.save(user);
|
|
238
|
+
|
|
239
|
+
expect(user.createdAt)
|
|
240
|
+
.toBeInstanceOf(Date);
|
|
241
|
+
|
|
242
|
+
expect(user.updatedAt)
|
|
243
|
+
.toBeInstanceOf(Date);
|
|
244
|
+
|
|
245
|
+
expect(user.createdAt.getTime())
|
|
246
|
+
.toBe(createdAt);
|
|
247
|
+
|
|
248
|
+
expect(user.updatedAt.getTime())
|
|
249
|
+
.toBeGreaterThan(updatedAt);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
})
|