Skip to content

Commit 7bc44ec

Browse files
Merge pull request #1 from codingbotPark/feature/typed-read-write
Feature/typed read write
2 parents 5c5ca6b + 88b682a commit 7bc44ec

7 files changed

Lines changed: 71 additions & 64 deletions

File tree

packages/main/src/config/SchemaConfig.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import { DataTypes, LiteralDataTypes } from "@src/core/DDL/abstracts/BaseFieldBuilder";
12
import Schema from "@src/core/DDL/implements/Schema"
23

34
export type MissingSchemaStrategy = 'create' | 'ignore' | 'error'
45

56
// 이 인터페이스는 이제 Configs.ts에서 직접 정의되므로 주석 처리하거나 삭제할 수 있습니다.
67
export interface SchemaConfigOptions<T extends Schema[]> {
78
onMissingSchema?: MissingSchemaStrategy;
9+
recordDetailType?:boolean,
10+
parseDetailType?:boolean,
811
schemas?: T;
912
}
1013

@@ -13,11 +16,38 @@ export type SchemaMap<T extends Schema[]> = {
1316
}
1417

1518
class SchemaConfig<T extends Schema[]>{
16-
missingSchemaStartegy: MissingSchemaStrategy
1719
readonly DEFAULT_MISSING_STRATEGY: MissingSchemaStrategy = 'create'
20+
21+
missingSchemaStartegy: MissingSchemaStrategy
22+
recordDetailType: boolean
23+
parseDetailType: boolean
1824

1925
readonly schemaList: T;
2026
readonly schemaMap: SchemaMap<T>;
27+
schemaSetted:boolean
28+
29+
// type parser for spreadsheet values
30+
typeParsers:Record<LiteralDataTypes, (value:string) => DataTypes> = {
31+
boolean:(value) => value === "true",
32+
date: (value) => new Date(value),
33+
number: (value) => Number(value),
34+
string: (value) => value,
35+
}
36+
37+
constructor({
38+
onMissingSchema = this.DEFAULT_MISSING_STRATEGY,
39+
recordDetailType = true,
40+
parseDetailType = true,
41+
schemas = [] as unknown as T,
42+
}: SchemaConfigOptions<T>) {
43+
this.missingSchemaStartegy = onMissingSchema;
44+
this.recordDetailType = recordDetailType;
45+
this.parseDetailType = parseDetailType;
46+
47+
this.schemaList = schemas;
48+
this.schemaSetted = this.schemaList.length > 0;
49+
this.schemaMap = this.makeSchemaMap(this.schemaList);
50+
}
2151

2252
private makeSchemaMap(schemas:T){
2353
const schemaMap = schemas.reduce((schemaDefinition, schema) => {
@@ -27,12 +57,6 @@ class SchemaConfig<T extends Schema[]>{
2757
}, {} as SchemaMap<T>)
2858
return schemaMap
2959
}
30-
31-
constructor(options: SchemaConfigOptions<T>) {
32-
this.missingSchemaStartegy = options.onMissingSchema ?? this.DEFAULT_MISSING_STRATEGY
33-
this.schemaList = (options.schemas ?? []) as T
34-
this.schemaMap = this.makeSchemaMap(this.schemaList)
35-
}
3660
}
3761
export default SchemaConfig
3862

packages/main/src/config/SpreadConfig.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ class SpreadConfig{
2525
return false
2626
}
2727

28+
// Google Sheets 시리얼 번호로 변환하는 헬퍼 함수
29+
static jsDateToSheetsSerial(jsDate:Date) {
30+
const sheetsEpoch = new Date(1899, 11, 30, 0, 0, 0);
31+
const diffMillis = jsDate.getTime() - sheetsEpoch.getTime();
32+
const millisPerDay = 24 * 60 * 60 * 1000;
33+
let sheetsSerial = diffMillis / millisPerDay;
34+
if (jsDate.getFullYear() > 1900 || (jsDate.getFullYear() === 1900 && jsDate.getMonth() > 1)) {
35+
sheetsSerial += 1;
36+
}
37+
38+
return sheetsSerial;
39+
}
2840

2941
/**
3042
* instance properties
@@ -72,14 +84,16 @@ class SpreadConfig{
7284
}
7385

7486
async batchUpdateQuery(requests: sheets_v4.Schema$Request[], spreadsheetId?: string,) {
75-
const response = await this.API.spreadsheets.batchUpdate({
76-
spreadsheetId:spreadsheetId ?? this.ID,
77-
requestBody: { requests }
78-
});
87+
const request = {
88+
spreadsheetId:spreadsheetId ?? this.ID,
89+
requestBody: { requests }
90+
}
91+
const response = await this.API.spreadsheets.batchUpdate(request);
7992
if (response.status !== 200) throw new Error("Batch failed");
8093
return response.data.replies;
8194
}
8295

96+
8397
private checkFormat(options:SpreadConfigOptions){
8498
if (!this.isValidEmail(options.email)){
8599
throw Error("Invalid email format")

packages/main/src/core/DDL/SchemaManager.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,16 +149,17 @@ class SchemaManager<T extends Schema[]> {
149149

150150
if (dataType !== "number" && dataType !== "date") continue
151151

152-
const startColumnIndex = this.config.sheet.columnToNumber(this.config.sheet.DEFAULT_RECORDING_START_COLUMN) + idx
153-
const request = SheetQueries.repeatTypedCell(sheetId, dataType, {
154-
startRowIndex: this.config.sheet.DATA_STARTING_ROW,
152+
const startColumnIndex = this.config.sheet.columnToNumber(this.config.sheet.DEFAULT_RECORDING_START_COLUMN) - 1 + idx
153+
const request = SheetQueries.setNumberTypedCell(dataType, {
154+
sheetId,
155+
startRowIndex: this.config.sheet.DATA_STARTING_ROW - 1,
155156
startColumnIndex,
156157
endColumnIndex: startColumnIndex + 1
157158
})
158159
setTypedColumnRequests.push(request)
159160
}
160161
}
161-
this.config.spread.batchUpdateQuery(setTypedColumnRequests)
162+
await this.config.spread.batchUpdateQuery(setTypedColumnRequests)
162163
console.log("set type to column successfully")
163164

164165
return result

packages/main/src/core/DML/implements/InsertBuilder.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Schema from "@src/core/DDL/implements/Schema";
44
import AndAbleQueryStore from "../abstracts/mixins/AndAbleQueryStore";
55
import QueryStore, { BasicQueryQueueType } from "../abstracts/QueryStore";
66
import { SchemaMap } from "@src/config/SchemaConfig";
7+
import SpreadConfig from "@src/config/SpreadConfig";
78

89
interface InsertQueryQueueType extends BasicQueryQueueType{
910
insertValues:DataTypes[]
@@ -75,17 +76,16 @@ class SettedInsertBuilder<T extends Schema[], SheetName extends T[number]['sheet
7576
newGroupedBySheet.set(sheetName, values); // 스키마 없으면 원본 그대로
7677
continue;
7778
}
78-
79+
console.log(values)
7980
const newValues: DataTypes[][] = values.map(row => {
8081
const newRow = [...row]; // 원본 행 복사
8182

8283
for (const fieldName in schema.fields) {
8384
const field = schema.fields[fieldName];
8485
if (field.dataType === 'date' && field.timestampAtCreated) {
8586
const columnIndex = field.columnOrder - 1; // columnOrder는 1부터 시작, 배열 인덱스는 0부터 시작
86-
8787
const now = new Date();
88-
newRow[columnIndex] = this.dateToGoogleSheetsSerial(now);
88+
newRow[columnIndex] = SpreadConfig.jsDateToSheetsSerial(now); // not working now
8989
}
9090
}
9191
return newRow;
@@ -95,22 +95,6 @@ class SettedInsertBuilder<T extends Schema[], SheetName extends T[number]['sheet
9595
return newGroupedBySheet;
9696
}
9797

98-
// Google Sheets 시리얼 번호로 변환하는 헬퍼 함수
99-
private dateToGoogleSheetsSerial(date: Date): number {
100-
// Google Sheets epoch: December 30, 1899
101-
// JavaScript epoch: January 1, 1970
102-
// 1900년 2월 29일 버그 (Lotus 1-2-3에서 유래, Excel/Sheets가 계승) 때문에 25569를 더함
103-
// (1900년은 윤년이 아니지만, Excel/Sheets는 윤년으로 간주)
104-
const excelEpoch = new Date(Date.UTC(1899, 11, 30)); // December 30, 1899
105-
const msPerDay = 24 * 60 * 60 * 1000;
106-
const serial = (date.getTime() - excelEpoch.getTime()) / msPerDay;
107-
108-
// 1900년 2월 29일 버그 보정 (1900년 3월 1일 이후 날짜에만 해당)
109-
// 1900년 3월 1일은 시리얼 61
110-
if (serial >= 61) {
111-
return serial + 1;
112-
}
113-
return serial;
114-
}
98+
11599

116100
}

packages/main/src/core/DML/implements/SelectBuilder.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ extends WhereableAndQueryStore<T, SelectBuilder<T>, SelectQueryQueueType>{
5050
const composedRange = this.config.sheet.composeRange(query.sheetName, this.config.sheet.DATA_STARTING_ROW, specifiedColumn)
5151
return composedRange
5252
})
53-
console.log("composedRanges",composedRanges)
5453
const requestBody = this.makeRequestBody(composedRanges)
55-
console.log("requestBody",requestBody)
5654

5755
const response = await this.config.spread.API.spreadsheets.values.batchGetByDataFilter({
5856
spreadsheetId:this.config.spread.ID,
@@ -85,19 +83,8 @@ extends WhereableAndQueryStore<T, SelectBuilder<T>, SelectQueryQueueType>{
8583
if (!(queriedFrom in this.config.schema.schemaMap)) return sheetValue
8684

8785
const currentSchema = this.config.schema.schemaMap[queriedFrom as keyof SchemaMap<T>]
88-
const typeConverters:((value:string)=>DataTypes)[] = currentSchema.orderedColumns.map((column) => {
89-
const type = currentSchema.fields[column].dataType
90-
switch (type){
91-
case "boolean":
92-
return (value:string) => value.toLowerCase() === "true"
93-
case "date":
94-
return (value:string) => new Date(value)
95-
case "number":
96-
return (value:string) => Number(value)
97-
default:
98-
return (value:string) => value
99-
}
100-
})
86+
// orderColumns 를 기준으로 typeParser메서드들이 indexing된 배열을
87+
const typeConverters:((value:string)=>DataTypes)[] = currentSchema.orderedColumns.map((column) => this.config.schema.typeParsers[currentSchema.fields[column].dataType])
10188

10289
const result = sheetValue.map((row) => row.map((value,idx) => typeConverters[idx](value)))
10390
return result

packages/main/src/generators/SheetQueries.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,28 @@ export class SheetQueries {
2020
}
2121
}
2222

23-
static repeatTypedCell(
24-
sheetId:number,
23+
static setNumberTypedCell(
2524
type:"number" | "date",
26-
range:Omit<sheets_v4.Schema$GridRange, "sheetId"> = {endRowIndex:1000000})
25+
range:sheets_v4.Schema$GridRange)
2726
:sheets_v4.Schema$Request{
2827
const numberFormat = {
2928
"number":{
30-
type:"NUMBER"
29+
type:"NUMBER",
3130
},
3231
"date":{
3332
type:"DATE_TIME",
34-
pattern: "yyyy-mm-dd hh:mm:ss"
3533
}
3634
}
37-
3835
return {
3936
repeatCell:{
4037
range:{
41-
sheetId,
42-
...range,
38+
startRowIndex:2,
39+
endRowIndex:1000000,
40+
...range
4341
},
4442
cell:{
4543
userEnteredFormat: {
46-
numberFormat:numberFormat[type]
44+
numberFormat:numberFormat[type],
4745
}
4846
},
4947
fields:"userEnteredFormat.numberFormat"

packages/test/index.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import createSpreadsheetClient, { Credentials, defineTable, fieldBuilder } from "spreadsheet-orm"
22
import credentials from "./security/credentials.json"
3-
43
const connectionParameters:Credentials = credentials
54

65
const userSchemaFields = {
@@ -36,14 +35,14 @@ const spreadsheetClient = createSpreadsheetClient({
3635
})
3736
// spreadsheetClient.configs.schema.schemaMap.cars
3837
await spreadsheetClient.schemaManager.sync({mode:"force"})
39-
await spreadsheetClient.queryBuilder.insert(["volvo", 1960]).into("cars").execute()
40-
// await spreadsheetClient.queryBuilder.insert(["volvo",1960]).into("cars").execute()
38+
39+
await spreadsheetClient.queryBuilder.insert(["volvo",1960]).into("cars").execute()
4140
// await spreadsheetClient.queryBuilder.delete().from("cars").where((data) => data[2] === "1960").execute() // index0 = index
4241
// await spreadsheetClient.queryBuilder.update(["hyundai", 2000]).from("cars").execute()
4342
// await spreadsheetClient.queryBuilder.insert(["volvo",1960]).into("cars").and(["hyundai", 2020]).into("cars").execute()
4443
// // await spreadsheetClient.queryBuilder.insert(["volve", 1960]).into("cars").and()
45-
const result = await spreadsheetClient.queryBuilder.select().from("cars").and().from("user").execute({detail:false})
46-
console.log(result)
44+
// const result = await spreadsheetClient.queryBuilder.select().from("cars").and().from("user").execute({detail:false})
45+
// console.log(result)
4746

4847

4948
// await spreadsheetClient.queryBuilder.update()

0 commit comments

Comments
 (0)