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,290 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ColumnType,
|
|
3
|
+
DataSource,
|
|
4
|
+
Driver,
|
|
5
|
+
EntityMetadata,
|
|
6
|
+
ObjectLiteral,
|
|
7
|
+
QueryRunner,
|
|
8
|
+
ReplicationMode,
|
|
9
|
+
Table,
|
|
10
|
+
TableColumn,
|
|
11
|
+
TableForeignKey,
|
|
12
|
+
View
|
|
13
|
+
} from "typeorm";
|
|
14
|
+
import type { CteCapabilities } from "typeorm/driver/types/CteCapabilities.js";
|
|
15
|
+
import type { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults.js";
|
|
16
|
+
import type { IsolationLevel } from "typeorm/driver/types/IsolationLevel.js";
|
|
17
|
+
import type { MappedColumnTypes } from "typeorm/driver/types/MappedColumnTypes.js";
|
|
18
|
+
import type { UpsertType } from "typeorm/driver/types/UpsertType.js";
|
|
19
|
+
import type { SchemaBuilder } from "typeorm/schema-builder/SchemaBuilder.js";
|
|
20
|
+
import type { ColumnMetadata } from "typeorm/metadata/ColumnMetadata.js";
|
|
21
|
+
import type { ReturningType } from "typeorm/driver/types/ReturningType.js";
|
|
22
|
+
import type { GoogleSheetsClient } from "./types.js";
|
|
23
|
+
import { GoogleSheetsSchemaBuilder } from "./schema-builder.js";
|
|
24
|
+
import { GoogleSheetsInvalidMetadataError } from "./error.js";
|
|
25
|
+
import { GoogleSheetsQueryRunner } from "./query/runner.js";
|
|
26
|
+
|
|
27
|
+
export class GoogleSheetsDriver implements Driver
|
|
28
|
+
{
|
|
29
|
+
constructor(
|
|
30
|
+
public readonly dataSource: DataSource,
|
|
31
|
+
public readonly client: GoogleSheetsClient,
|
|
32
|
+
) { }
|
|
33
|
+
|
|
34
|
+
version = '1.0';
|
|
35
|
+
database = undefined;
|
|
36
|
+
schema = undefined;
|
|
37
|
+
isReplicated = false;
|
|
38
|
+
treeSupport = false;
|
|
39
|
+
transactionSupport = 'none' as const;
|
|
40
|
+
supportedDataTypes: ColumnType[] = [
|
|
41
|
+
'string',
|
|
42
|
+
'number',
|
|
43
|
+
'boolean',
|
|
44
|
+
'date',
|
|
45
|
+
'uuid',
|
|
46
|
+
'int'
|
|
47
|
+
];
|
|
48
|
+
supportedIsolationLevels: readonly IsolationLevel[] = [];
|
|
49
|
+
supportedUpsertTypes: UpsertType[] = [];
|
|
50
|
+
supportedOnDeleteTypes = [];
|
|
51
|
+
supportedOnUpdateTypes = [];
|
|
52
|
+
dataTypeDefaults: DataTypeDefaults = {};
|
|
53
|
+
spatialTypes: ColumnType[] = [];
|
|
54
|
+
withLengthColumnTypes: ColumnType[] = [];
|
|
55
|
+
withPrecisionColumnTypes: ColumnType[] = [];
|
|
56
|
+
withScaleColumnTypes: ColumnType[] = [];
|
|
57
|
+
mappedDataTypes: MappedColumnTypes = {
|
|
58
|
+
createDate: Date,
|
|
59
|
+
createDateDefault: 'CURRENT_TIMESTAMP',
|
|
60
|
+
updateDate: Date,
|
|
61
|
+
updateDateDefault: 'CURRENT_TIMESTAMP',
|
|
62
|
+
deleteDate: Date,
|
|
63
|
+
deleteDateNullable: true,
|
|
64
|
+
version: Number,
|
|
65
|
+
treeLevel: Number,
|
|
66
|
+
migrationId: Number,
|
|
67
|
+
migrationTimestamp: Number,
|
|
68
|
+
migrationName: String,
|
|
69
|
+
cacheId: Number,
|
|
70
|
+
cacheIdentifier: String,
|
|
71
|
+
cacheTime: Number,
|
|
72
|
+
cacheDuration: Number,
|
|
73
|
+
cacheQuery: String,
|
|
74
|
+
cacheResult: String,
|
|
75
|
+
metadataType: String,
|
|
76
|
+
metadataDatabase: String,
|
|
77
|
+
metadataSchema: String,
|
|
78
|
+
metadataTable: String,
|
|
79
|
+
metadataName: String,
|
|
80
|
+
metadataValue: String,
|
|
81
|
+
};
|
|
82
|
+
parametersPrefix = '@';
|
|
83
|
+
maxAliasLength = 255;
|
|
84
|
+
cteCapabilities: CteCapabilities = {
|
|
85
|
+
enabled: false,
|
|
86
|
+
requiresRecursiveHint: false,
|
|
87
|
+
};
|
|
88
|
+
dummyTableName = 'dual';
|
|
89
|
+
|
|
90
|
+
get options()
|
|
91
|
+
{
|
|
92
|
+
return this.dataSource.options;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async connect(): Promise<void>
|
|
96
|
+
{
|
|
97
|
+
await this.client.connect();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async afterConnect(): Promise<void> { }
|
|
101
|
+
|
|
102
|
+
async disconnect(): Promise<void>
|
|
103
|
+
{
|
|
104
|
+
await this.client.disconnect();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
createSchemaBuilder(): SchemaBuilder
|
|
108
|
+
{
|
|
109
|
+
return new GoogleSheetsSchemaBuilder(this.dataSource, this);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
createQueryRunner(mode: ReplicationMode): QueryRunner
|
|
113
|
+
{
|
|
114
|
+
return new GoogleSheetsQueryRunner(this, mode);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
escapeQueryWithParameters(sql: string, parameters: ObjectLiteral): [string, any[]]
|
|
118
|
+
{
|
|
119
|
+
const values: any[] = [];
|
|
120
|
+
|
|
121
|
+
const escapedSql =
|
|
122
|
+
sql.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_match, parameterName: string) =>
|
|
123
|
+
{
|
|
124
|
+
const index = values.length;
|
|
125
|
+
|
|
126
|
+
values.push(parameters[parameterName],
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return `:orm_param_${index}`;
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
return [escapedSql, values];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
escape(name: string): string
|
|
137
|
+
{
|
|
138
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
buildTableName(tableName: string, schema?: string, database?: string): string
|
|
142
|
+
{
|
|
143
|
+
return tableName;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
parseTableName(target: EntityMetadata | Table | View | TableForeignKey | string)
|
|
147
|
+
: { tableName: string; schema?: string; database?: string; }
|
|
148
|
+
{
|
|
149
|
+
if (typeof target === 'string') return { tableName: target };
|
|
150
|
+
if ('tableName' in target && target.tableName) return { tableName: target.tableName };
|
|
151
|
+
if ('name' in target && typeof target.name === 'string') return { tableName: target.name };
|
|
152
|
+
|
|
153
|
+
throw new GoogleSheetsInvalidMetadataError('Unable to parse table name.');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
preparePersistentValue(value: any, column: ColumnMetadata)
|
|
157
|
+
{
|
|
158
|
+
return value
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
prepareHydratedValue(value: any, column: ColumnMetadata)
|
|
162
|
+
{
|
|
163
|
+
if (value === null || value === undefined)
|
|
164
|
+
{
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (column.type === Number)
|
|
169
|
+
{
|
|
170
|
+
return Number(value);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (column.type === Boolean)
|
|
174
|
+
{
|
|
175
|
+
if (typeof value === 'boolean')
|
|
176
|
+
{
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (typeof value === 'string')
|
|
181
|
+
{
|
|
182
|
+
return value.toUpperCase() === 'TRUE';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return Boolean(value);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (column.type === Date)
|
|
189
|
+
{
|
|
190
|
+
if (value instanceof Date)
|
|
191
|
+
{
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return new Date(value);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return value;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
normalizeType(column: {
|
|
202
|
+
type?: ColumnType | string;
|
|
203
|
+
length?: number | string;
|
|
204
|
+
precision?: number | null;
|
|
205
|
+
scale?: number;
|
|
206
|
+
isArray?: boolean;
|
|
207
|
+
}): string
|
|
208
|
+
{
|
|
209
|
+
if (column.type === String) return 'string';
|
|
210
|
+
if (column.type === Number) return 'number';
|
|
211
|
+
if (column.type === Boolean) return 'boolean';
|
|
212
|
+
if (column.type === Date) return 'date';
|
|
213
|
+
|
|
214
|
+
return String(column.type ?? 'string');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
normalizeDefault(columnMetadata: ColumnMetadata): string | undefined
|
|
218
|
+
{
|
|
219
|
+
if (columnMetadata.default === undefined) return undefined;
|
|
220
|
+
|
|
221
|
+
return String(columnMetadata.default);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
normalizeIsUnique(column: ColumnMetadata): boolean
|
|
225
|
+
{
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
getColumnLength(column: ColumnMetadata): string
|
|
230
|
+
{
|
|
231
|
+
return '';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
createFullType(column: TableColumn): string
|
|
235
|
+
{
|
|
236
|
+
return column.type;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async obtainMasterConnection(): Promise<any>
|
|
240
|
+
{
|
|
241
|
+
return this.client
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async obtainSlaveConnection(): Promise<any>
|
|
245
|
+
{
|
|
246
|
+
return this.client
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
createGeneratedMap(metadata: EntityMetadata, insertResult: any, entityIndex?: number, entityNum?: number)
|
|
250
|
+
: ObjectLiteral | undefined
|
|
251
|
+
{
|
|
252
|
+
if (!insertResult) return undefined;
|
|
253
|
+
|
|
254
|
+
const generatedMap: ObjectLiteral = {};
|
|
255
|
+
|
|
256
|
+
for (const column of metadata.generatedColumns)
|
|
257
|
+
{
|
|
258
|
+
const value = insertResult[column.databaseName];
|
|
259
|
+
|
|
260
|
+
if (value !== undefined) generatedMap[column.propertyName] = value;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return generatedMap;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
findChangedColumns(tableColumns: TableColumn[], columnMetadatas: ColumnMetadata[]): ColumnMetadata[]
|
|
267
|
+
{
|
|
268
|
+
return []
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
isReturningSqlSupported(returningType: ReturningType): boolean
|
|
272
|
+
{
|
|
273
|
+
return false
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
isUUIDGenerationSupported(): boolean
|
|
277
|
+
{
|
|
278
|
+
return true
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
isFullTextColumnTypeSupported(): boolean
|
|
282
|
+
{
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
createParameter(parameterName: string, index: number): string
|
|
287
|
+
{
|
|
288
|
+
return `:${parameterName}`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export class GoogleSheetsDriverError extends Error
|
|
2
|
+
{
|
|
3
|
+
constructor(
|
|
4
|
+
message: string,
|
|
5
|
+
options?: ErrorOptions,
|
|
6
|
+
)
|
|
7
|
+
{
|
|
8
|
+
super(message, options);
|
|
9
|
+
|
|
10
|
+
this.name = 'GoogleSheetsDriverError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class GoogleSheetsAuthenticationError extends GoogleSheetsDriverError
|
|
15
|
+
{
|
|
16
|
+
constructor(
|
|
17
|
+
message = 'Google Sheets authentication failed.',
|
|
18
|
+
options?: ErrorOptions,
|
|
19
|
+
)
|
|
20
|
+
{
|
|
21
|
+
super(message, options);
|
|
22
|
+
|
|
23
|
+
this.name = 'GoogleSheetsAuthenticationError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class GoogleSheetsSpreadsheetNotFoundError extends GoogleSheetsDriverError
|
|
28
|
+
{
|
|
29
|
+
constructor(
|
|
30
|
+
message = 'Google Sheets spreadsheet not found.',
|
|
31
|
+
options?: ErrorOptions,
|
|
32
|
+
)
|
|
33
|
+
{
|
|
34
|
+
super(message, options);
|
|
35
|
+
|
|
36
|
+
this.name = 'GoogleSheetsSpreadsheetNotFoundError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class GoogleSheetsWorksheetNotFoundError extends GoogleSheetsDriverError
|
|
41
|
+
{
|
|
42
|
+
constructor(
|
|
43
|
+
message: string,
|
|
44
|
+
options?: ErrorOptions,
|
|
45
|
+
)
|
|
46
|
+
{
|
|
47
|
+
super(message, options);
|
|
48
|
+
|
|
49
|
+
this.name = 'GoogleSheetsWorksheetNotFoundError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class GoogleSheetsParseError extends GoogleSheetsDriverError
|
|
54
|
+
{
|
|
55
|
+
constructor(
|
|
56
|
+
message: string,
|
|
57
|
+
options?: ErrorOptions,
|
|
58
|
+
)
|
|
59
|
+
{
|
|
60
|
+
super(message, options);
|
|
61
|
+
|
|
62
|
+
this.name = 'GoogleSheetsParseError';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class GoogleSheetsInvalidRangeError extends GoogleSheetsDriverError
|
|
67
|
+
{
|
|
68
|
+
constructor(
|
|
69
|
+
message = 'Google Sheets range is invalid.',
|
|
70
|
+
options?: ErrorOptions,
|
|
71
|
+
)
|
|
72
|
+
{
|
|
73
|
+
super(message, options);
|
|
74
|
+
|
|
75
|
+
this.name = 'GoogleSheetsInvalidRangeError';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class GoogleSheetsInvalidMetadataError extends GoogleSheetsDriverError
|
|
80
|
+
{
|
|
81
|
+
constructor(
|
|
82
|
+
message = 'Google Sheets metadata is invalid.',
|
|
83
|
+
options?: ErrorOptions,
|
|
84
|
+
)
|
|
85
|
+
{
|
|
86
|
+
super(message, options);
|
|
87
|
+
|
|
88
|
+
this.name = 'GoogleSheetsInvalidMetadataError';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class GoogleSheetsDuplicatePrimaryKeyError extends GoogleSheetsDriverError
|
|
93
|
+
{
|
|
94
|
+
constructor(
|
|
95
|
+
message = 'Duplicate primary key.',
|
|
96
|
+
options?: ErrorOptions,
|
|
97
|
+
)
|
|
98
|
+
{
|
|
99
|
+
super(message, options);
|
|
100
|
+
|
|
101
|
+
this.name = 'GoogleSheetsDuplicatePrimaryKeyError';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export class GoogleSheetsMissingPrimaryKeyError extends GoogleSheetsDriverError
|
|
106
|
+
{
|
|
107
|
+
constructor(
|
|
108
|
+
message = 'Missing primary key.',
|
|
109
|
+
options?: ErrorOptions,
|
|
110
|
+
)
|
|
111
|
+
{
|
|
112
|
+
super(message, options);
|
|
113
|
+
this.name = 'GoogleSheetsMissingPrimaryKeyError';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class GoogleSheetsUnsupportedOperationError extends GoogleSheetsDriverError
|
|
118
|
+
{
|
|
119
|
+
constructor(
|
|
120
|
+
operation: string,
|
|
121
|
+
options?: ErrorOptions,
|
|
122
|
+
)
|
|
123
|
+
{
|
|
124
|
+
super(
|
|
125
|
+
`Google Sheets operation "${operation}" is not supported.`,
|
|
126
|
+
options,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
this.name = 'GoogleSheetsUnsupportedOperationError';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export class GoogleSheetsGenaralError extends GoogleSheetsDriverError
|
|
134
|
+
{
|
|
135
|
+
constructor(
|
|
136
|
+
message: string,
|
|
137
|
+
options?: ErrorOptions,
|
|
138
|
+
)
|
|
139
|
+
{
|
|
140
|
+
super(message, options);
|
|
141
|
+
|
|
142
|
+
this.name = 'GoogleSheetsGenaralError';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { GoogleSheetsClient, GoogleSheetsRow, GoogleSheetsSheetMetadata } from "./types.js";
|
|
2
|
+
import { validateWorksheetName } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
export class Memory implements GoogleSheetsClient
|
|
5
|
+
{
|
|
6
|
+
private readonly sheets = new Map<string, GoogleSheetsRow[]>();
|
|
7
|
+
|
|
8
|
+
private readonly headers = new Map<string, string[]>();
|
|
9
|
+
|
|
10
|
+
private readonly sheetMetadata = new Map<string, GoogleSheetsSheetMetadata>();
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
initialSheets: Record<string, GoogleSheetsRow[]> = {},
|
|
14
|
+
)
|
|
15
|
+
{
|
|
16
|
+
let sheetId = 1;
|
|
17
|
+
|
|
18
|
+
for (const [sheetName, rows] of Object.entries(initialSheets))
|
|
19
|
+
{
|
|
20
|
+
this.sheets.set(sheetName, structuredClone(rows));
|
|
21
|
+
|
|
22
|
+
this.sheetMetadata.set(sheetName, { sheetId, title: sheetName });
|
|
23
|
+
|
|
24
|
+
sheetId++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async connect(): Promise<void> { }
|
|
29
|
+
|
|
30
|
+
async disconnect(): Promise<void> { }
|
|
31
|
+
|
|
32
|
+
async hasSheet(sheetName: string): Promise<boolean>
|
|
33
|
+
{
|
|
34
|
+
return this.sheets.has(sheetName);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async createSheet(sheetName: string): Promise<void>
|
|
38
|
+
{
|
|
39
|
+
validateWorksheetName(sheetName);
|
|
40
|
+
|
|
41
|
+
if (this.sheets.has(sheetName)) return;
|
|
42
|
+
|
|
43
|
+
this.sheets.set(sheetName, []);
|
|
44
|
+
|
|
45
|
+
this.headers.set(sheetName, []);
|
|
46
|
+
|
|
47
|
+
this.sheetMetadata.set(sheetName, { sheetId: this.sheetMetadata.size + 1, title: sheetName });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getSheetMetadata(sheetName: string): Promise<GoogleSheetsSheetMetadata | null>
|
|
51
|
+
{
|
|
52
|
+
const metadata = this.sheetMetadata.get(sheetName);
|
|
53
|
+
|
|
54
|
+
if (!metadata) return null;
|
|
55
|
+
|
|
56
|
+
return structuredClone(metadata);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getHeaders(sheetName: string): Promise<string[]>
|
|
60
|
+
{
|
|
61
|
+
return structuredClone(this.headers.get(sheetName) ?? []);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async insertHeaders(sheetName: string, headers: string[]): Promise<void>
|
|
65
|
+
{
|
|
66
|
+
this.headers.set(sheetName, structuredClone(headers));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async renameSheet(oldSheetName: string, newSheetName: string): Promise<void>
|
|
70
|
+
{
|
|
71
|
+
validateWorksheetName(newSheetName);
|
|
72
|
+
|
|
73
|
+
if (!this.sheets.has(oldSheetName)) return;
|
|
74
|
+
|
|
75
|
+
const rows = this.sheets.get(oldSheetName)!;
|
|
76
|
+
const headers = this.headers.get(oldSheetName) ?? [];
|
|
77
|
+
const metadata = this.sheetMetadata.get(oldSheetName);
|
|
78
|
+
|
|
79
|
+
this.sheets.delete(oldSheetName);
|
|
80
|
+
this.headers.delete(oldSheetName);
|
|
81
|
+
this.sheetMetadata.delete(oldSheetName);
|
|
82
|
+
|
|
83
|
+
this.sheets.set(newSheetName, rows);
|
|
84
|
+
this.headers.set(newSheetName, headers);
|
|
85
|
+
|
|
86
|
+
if (metadata) this.sheetMetadata.set(newSheetName, { ...metadata, title: newSheetName });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async deleteSheet(sheetName: string): Promise<void>
|
|
90
|
+
{
|
|
91
|
+
this.sheets.delete(sheetName);
|
|
92
|
+
this.headers.delete(sheetName);
|
|
93
|
+
this.sheetMetadata.delete(sheetName);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getRows(sheetName: string): Promise<GoogleSheetsRow[]>
|
|
97
|
+
{
|
|
98
|
+
return structuredClone(this.sheets.get(sheetName) ?? []);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async insertRows(sheetName: string, rows: GoogleSheetsRow[]): Promise<void>
|
|
102
|
+
{
|
|
103
|
+
const currentRows = this.sheets.get(sheetName) ?? [];
|
|
104
|
+
|
|
105
|
+
currentRows.push(...structuredClone(rows));
|
|
106
|
+
|
|
107
|
+
this.sheets.set(sheetName, currentRows);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async updateRows(sheetName: string, predicate: (row: GoogleSheetsRow) => boolean, update: Partial<GoogleSheetsRow>)
|
|
111
|
+
: Promise<number>
|
|
112
|
+
{
|
|
113
|
+
const rows = this.sheets.get(sheetName) ?? [];
|
|
114
|
+
|
|
115
|
+
let affected = 0;
|
|
116
|
+
|
|
117
|
+
for (const row of rows)
|
|
118
|
+
{
|
|
119
|
+
if (!predicate(row)) continue;
|
|
120
|
+
|
|
121
|
+
Object.assign(row, update);
|
|
122
|
+
|
|
123
|
+
affected++;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return affected;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async deleteRows(sheetName: string, predicate: (row: GoogleSheetsRow) => boolean): Promise<number>
|
|
130
|
+
{
|
|
131
|
+
const rows = this.sheets.get(sheetName) ?? [];
|
|
132
|
+
|
|
133
|
+
const remainingRows: GoogleSheetsRow[] = [];
|
|
134
|
+
let affected = 0;
|
|
135
|
+
|
|
136
|
+
for (const row of rows)
|
|
137
|
+
{
|
|
138
|
+
if (predicate(row))
|
|
139
|
+
{
|
|
140
|
+
affected++;
|
|
141
|
+
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
remainingRows.push(row);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.sheets.set(sheetName, remainingRows);
|
|
149
|
+
|
|
150
|
+
return affected;
|
|
151
|
+
}
|
|
152
|
+
}
|