@an5/adapters 0.2.4 → 0.2.6
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/README.md +189 -304
- package/dist/an5Adapter.d.ts +85 -7
- package/dist/an5Adapter.js +736 -64
- package/dist/base/metadata.d.ts +10 -0
- package/dist/base/metadata.js +107 -0
- package/dist/base/sql.d.ts +19 -1
- package/dist/base/sql.js +152 -24
- package/dist/base/types.d.ts +4 -1
- package/dist/browser.d.ts +5 -2
- package/dist/browser.js +10 -2
- package/dist/googlesheets/adapter.d.ts +1 -0
- package/dist/googlesheets/adapter.js +9 -2
- package/dist/googlesheets/config.d.ts +4 -4
- package/dist/googlesheets/helpers.js +2 -0
- package/dist/googlesheets/parseConnectionString.js +5 -2
- package/dist/googlesheets/sqlExecutor.js +29 -24
- package/dist/googlesheets/tableClient.js +14 -5
- package/dist/index.d.ts +5 -1
- package/dist/index.js +12 -1
- package/dist/mssql/engine.js +8 -4
- package/dist/mysql/engine.js +1 -1
- package/dist/postgres/engine.js +1 -1
- package/dist/sqlite/browserEngine.d.ts +27 -0
- package/dist/sqlite/browserEngine.js +161 -0
- package/dist/sqlite/engine.js +15 -3
- package/dist/sqlite/index.d.ts +1 -0
- package/dist/sqlite/index.js +1 -0
- package/dist/typed.d.ts +5 -0
- package/dist/typed.js +2 -0
- package/dotnet/An5Adapter.cs +1089 -275
- package/dotnet/Base/Core.cs +340 -82
- package/dotnet/Mssql/MssqlEngine.cs +145 -144
- package/dotnet/Postgres/PostgresEngine.cs +121 -121
- package/golang/an5_adapter.go +314 -0
- package/golang/base/base.go +100 -0
- package/golang/base/metadata.go +116 -0
- package/golang/base/where.go +311 -0
- package/golang/go.mod +3 -0
- package/golang/mssql/mssql.go +32 -0
- package/golang/postgres/postgres.go +37 -0
- package/golang/sqlite/sqlite.go +14 -0
- package/golang/sqlite_test.go +42 -0
- package/golang/table_client.go +1329 -0
- package/package.json +18 -8
- package/python/an5_adapter.py +119 -97
- package/python/base/__init__.py +3 -3
- package/python/base/dialects.py +11 -8
- package/python/base/metadata.py +35 -10
- package/python/base/sql.py +172 -121
- package/python/mssql/__init__.py +1 -1
- package/python/mssql/provider.py +62 -62
- package/python/postgres/__init__.py +1 -1
- package/python/postgres/provider.py +15 -15
- package/python/table_client.py +619 -216
- package/typescript/src/an5Adapter.ts +1220 -505
- package/typescript/src/base/index.ts +3 -3
- package/typescript/src/base/metadata.ts +148 -28
- package/typescript/src/base/sql.ts +264 -98
- package/typescript/src/base/types.ts +27 -24
- package/typescript/src/browser.ts +10 -6
- package/typescript/src/config.ts +95 -95
- package/typescript/src/googlesheets/adapter.ts +269 -259
- package/typescript/src/googlesheets/an5SheetsAdapter.ts +3 -3
- package/typescript/src/googlesheets/config.ts +47 -47
- package/typescript/src/googlesheets/helpers.ts +144 -143
- package/typescript/src/googlesheets/index.ts +3 -3
- package/typescript/src/googlesheets/parseConnectionString.ts +48 -46
- package/typescript/src/googlesheets/retry.ts +126 -126
- package/typescript/src/googlesheets/sqlExecutor.ts +304 -301
- package/typescript/src/googlesheets/tableClient.ts +461 -455
- package/typescript/src/googlesheets/types.ts +6 -6
- package/typescript/src/index.ts +32 -19
- package/typescript/src/mssql/engine.ts +134 -132
- package/typescript/src/mssql/index.ts +1 -1
- package/typescript/src/mysql/engine.ts +80 -80
- package/typescript/src/mysql/index.ts +1 -1
- package/typescript/src/postgres/engine.ts +87 -87
- package/typescript/src/postgres/index.ts +1 -1
- package/typescript/src/sqlite/browserEngine.ts +192 -0
- package/typescript/src/sqlite/engine.ts +66 -55
- package/typescript/src/sqlite/index.ts +3 -1
- package/typescript/src/typed.ts +52 -0
|
@@ -1,259 +1,269 @@
|
|
|
1
|
-
import { An5SheetsAdapterConfig, resolveConfig } from './config';
|
|
2
|
-
import type { sheets_v4 } from 'googleapis';
|
|
3
|
-
import { execQuery } from './sqlExecutor';
|
|
4
|
-
import { SheetsTableClient } from './tableClient';
|
|
5
|
-
import { SheetMeta } from './types';
|
|
6
|
-
import { withRetry } from './retry';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
(err as any).
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
private
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async $
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
1
|
+
import { An5SheetsAdapterConfig, resolveConfig } from './config';
|
|
2
|
+
import type { sheets_v4 } from 'googleapis';
|
|
3
|
+
import { execQuery } from './sqlExecutor';
|
|
4
|
+
import { SheetsTableClient } from './tableClient';
|
|
5
|
+
import { SheetMeta } from './types';
|
|
6
|
+
import { withRetry } from './retry';
|
|
7
|
+
import { createAdapterProxy } from '../base/metadata';
|
|
8
|
+
|
|
9
|
+
// ─── Fetch-based API proxy for OAuth Access Token / API Key (browser-compatible) ──
|
|
10
|
+
|
|
11
|
+
function createFetchApi(spreadsheetId: string, accessToken?: string, apiKey?: string) {
|
|
12
|
+
const base = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}`;
|
|
13
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
14
|
+
if (accessToken) {
|
|
15
|
+
headers['Authorization'] = `Bearer ${accessToken}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function request<T>(method: string, path: string, body?: any): Promise<T> {
|
|
19
|
+
let url = path.startsWith('http') ? path : `${base}${path}`;
|
|
20
|
+
if (apiKey) {
|
|
21
|
+
const sep = url.includes('?') ? '&' : '?';
|
|
22
|
+
url += `${sep}key=${encodeURIComponent(apiKey)}`;
|
|
23
|
+
}
|
|
24
|
+
const opts: RequestInit = { method, headers };
|
|
25
|
+
if (body) opts.body = JSON.stringify(body);
|
|
26
|
+
const res = await fetch(url, opts);
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const text = await res.text().catch(() => '');
|
|
29
|
+
const err = new Error(`Google Sheets API error (${res.status}): ${text}`);
|
|
30
|
+
(err as any).status = res.status;
|
|
31
|
+
(err as any).body = text;
|
|
32
|
+
const retryAfter = res.headers.get('Retry-After');
|
|
33
|
+
if (retryAfter) (err as any).retryAfter = parseInt(retryAfter, 10);
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
const text = await res.text();
|
|
37
|
+
return text ? JSON.parse(text) : (undefined as T);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function fmtRange(range: string): string {
|
|
41
|
+
return `/${encodeURIComponent(range)}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
spreadsheets: {
|
|
46
|
+
get: async (params: any) => {
|
|
47
|
+
const data = await request<any>('GET', `?fields=${params.fields || ''}`);
|
|
48
|
+
return { data };
|
|
49
|
+
},
|
|
50
|
+
batchUpdate: async (params: any) => {
|
|
51
|
+
const data = await request<any>('POST', ':batchUpdate', params.requestBody);
|
|
52
|
+
return { data };
|
|
53
|
+
},
|
|
54
|
+
values: {
|
|
55
|
+
get: async (params: any) => {
|
|
56
|
+
const data = await request<any>('GET', fmtRange(params.range));
|
|
57
|
+
return { data };
|
|
58
|
+
},
|
|
59
|
+
update: async (params: any) => {
|
|
60
|
+
const qs = `?valueInputOption=${params.valueInputOption || 'RAW'}`;
|
|
61
|
+
const data = await request<any>('PUT', `${fmtRange(params.range)}${qs}`, params.requestBody);
|
|
62
|
+
return { data };
|
|
63
|
+
},
|
|
64
|
+
append: async (params: any) => {
|
|
65
|
+
const qs = `?valueInputOption=${params.valueInputOption || 'RAW'}&insertDataOption=${params.insertDataOption || 'INSERT_ROWS'}`;
|
|
66
|
+
await request<any>('POST', `${fmtRange(params.range)}:append${qs}`, params.requestBody);
|
|
67
|
+
return { data: { updates: { updatedRows: (params.requestBody?.values || []).length } } };
|
|
68
|
+
},
|
|
69
|
+
clear: async (params: any) => {
|
|
70
|
+
await request<any>('POST', `${fmtRange(params.range)}:clear`);
|
|
71
|
+
return { data: {} };
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Adapter ──────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
export class An5SheetsAdapter {
|
|
81
|
+
[key: string]: any;
|
|
82
|
+
private sheets: sheets_v4.Sheets | null = null;
|
|
83
|
+
private fetchApi: ReturnType<typeof createFetchApi> | null = null;
|
|
84
|
+
config: ReturnType<typeof resolveConfig>;
|
|
85
|
+
private sheetsCache: Promise<SheetMeta[]> | null = null;
|
|
86
|
+
|
|
87
|
+
constructor(config: An5SheetsAdapterConfig) {
|
|
88
|
+
this.config = resolveConfig(config);
|
|
89
|
+
// Dynamic model access (`db.user` / `db.User`), same as An5Adapter.
|
|
90
|
+
return createAdapterProxy(this, (name) => this.table(name));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private get isFetchMode(): boolean {
|
|
94
|
+
return !!(this.config as any).accessToken || !!(this.config as any).apiKey;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async getSheets(): Promise<any> {
|
|
98
|
+
if (this.isFetchMode) {
|
|
99
|
+
if (!this.fetchApi) {
|
|
100
|
+
this.fetchApi = createFetchApi(
|
|
101
|
+
this.config.spreadsheetId,
|
|
102
|
+
(this.config as any).accessToken,
|
|
103
|
+
(this.config as any).apiKey
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return this.fetchApi;
|
|
107
|
+
}
|
|
108
|
+
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
109
|
+
if (isBrowser) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
'Google Sheets adapter in browser mode requires "accessToken" or "apiKey" configuration.'
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (!this.sheets) {
|
|
115
|
+
const { google } = await import('googleapis');
|
|
116
|
+
const { clientEmail, privateKey } = this.config;
|
|
117
|
+
if (!clientEmail || !privateKey) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'Google Sheets JWT auth requires clientEmail and privateKey (or use accessToken/apiKey fetch mode).'
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const auth = new google.auth.JWT({
|
|
123
|
+
email: clientEmail,
|
|
124
|
+
key: privateKey,
|
|
125
|
+
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
|
|
126
|
+
});
|
|
127
|
+
this.sheets = google.sheets({ version: 'v4', auth });
|
|
128
|
+
}
|
|
129
|
+
return this.sheets;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async getApi() {
|
|
133
|
+
return this.getSheets();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
invalidateCache(): void {
|
|
137
|
+
this.sheetsCache = null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async listSheets(): Promise<string[]> {
|
|
141
|
+
const api = await this.getApi();
|
|
142
|
+
const res: any = await withRetry(() =>
|
|
143
|
+
api.spreadsheets.get({
|
|
144
|
+
spreadsheetId: this.config.spreadsheetId,
|
|
145
|
+
fields: 'sheets.properties.title',
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
return (res.data.sheets || []).map((s: any) => s.properties?.title || '');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async getSheetMeta(name: string): Promise<SheetMeta | null> {
|
|
152
|
+
const api = await this.getApi();
|
|
153
|
+
if (this.sheetsCache == null) {
|
|
154
|
+
this.sheetsCache = withRetry(async () => {
|
|
155
|
+
const res: any = await api.spreadsheets.get({
|
|
156
|
+
spreadsheetId: this.config.spreadsheetId,
|
|
157
|
+
fields: 'sheets.properties',
|
|
158
|
+
});
|
|
159
|
+
return (res.data.sheets || []).map((s: any) => ({
|
|
160
|
+
sheetId: s.properties?.sheetId ?? -1,
|
|
161
|
+
title: s.properties?.title ?? '',
|
|
162
|
+
})).filter((m: SheetMeta) => m.sheetId >= 0);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const all = await this.sheetsCache;
|
|
166
|
+
return all.find((m: SheetMeta) => m.title === name) || null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async deleteSheet(name: string): Promise<void> {
|
|
170
|
+
const meta = await this.getSheetMeta(name);
|
|
171
|
+
if (!meta) throw new Error(`Sheet "${name}" not found`);
|
|
172
|
+
const api = await this.getApi();
|
|
173
|
+
await withRetry(() =>
|
|
174
|
+
api.spreadsheets.batchUpdate({
|
|
175
|
+
spreadsheetId: this.config.spreadsheetId,
|
|
176
|
+
requestBody: {
|
|
177
|
+
requests: [{ deleteSheet: { sheetId: meta.sheetId } }],
|
|
178
|
+
},
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
this.invalidateCache();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async $connect(): Promise<void> {
|
|
185
|
+
await this.getSheets();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async $disconnect(): Promise<void> {
|
|
189
|
+
this.sheets = null;
|
|
190
|
+
this.fetchApi = null;
|
|
191
|
+
this.sheetsCache = null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async exec<T = any>(query: string, params?: Record<string, any>): Promise<T[]> {
|
|
195
|
+
return execQuery(this, query, params, undefined, undefined) as Promise<T[]>;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async $queryRawUnsafe<T = any>(query: string, ...values: any[]): Promise<T[]> {
|
|
199
|
+
const params: Record<string, any> = {};
|
|
200
|
+
values.forEach((v, i) => { params[`p_${i}`] = v; });
|
|
201
|
+
const resolved = query.replace(/@p_(\d+)/g, (_, i) => `@p_${i}`);
|
|
202
|
+
return execQuery(this, resolved, params, undefined, undefined) as Promise<T[]>;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async $executeRaw(query: string, ...values: any[]): Promise<number> {
|
|
206
|
+
const params: Record<string, any> = {};
|
|
207
|
+
values.forEach((v, i) => { params[`p_${i}`] = v; });
|
|
208
|
+
const resolved = query.replace(/@p_(\d+)/g, (_, i) => `@p_${i}`);
|
|
209
|
+
return execQuery(this, resolved, params, undefined, undefined) as Promise<number>;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async $executeRawUnsafe(query: string, ...values: any[]): Promise<number> {
|
|
213
|
+
return this.$executeRaw(query, ...values);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async $transaction<R>(fn: (tx: An5SheetsAdapter) => Promise<R>, _options?: { timeout?: number }): Promise<R>;
|
|
217
|
+
async $transaction<R>(list: Promise<R>[]): Promise<R[]>;
|
|
218
|
+
async $transaction(fn: any, _options?: any): Promise<any> {
|
|
219
|
+
if (typeof fn === 'function') return fn(this);
|
|
220
|
+
return Promise.all(fn);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
table<T = any>(modelName: string): SheetsTableClient<T> {
|
|
224
|
+
return new SheetsTableClient<T>(this, modelName, this.config.sheetMapping);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ── Raw range access ──────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
async readRange<T = any>(range: string): Promise<T[][]> {
|
|
230
|
+
const api = await this.getApi();
|
|
231
|
+
const res: any = await withRetry(() =>
|
|
232
|
+
api.spreadsheets.values.get({
|
|
233
|
+
spreadsheetId: this.config.spreadsheetId,
|
|
234
|
+
range,
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
return (res.data.values || []) as T[][];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async writeRange(range: string, values: any[][]): Promise<void> {
|
|
241
|
+
const api = await this.getApi();
|
|
242
|
+
await withRetry(() =>
|
|
243
|
+
api.spreadsheets.values.update({
|
|
244
|
+
spreadsheetId: this.config.spreadsheetId,
|
|
245
|
+
range,
|
|
246
|
+
valueInputOption: 'RAW',
|
|
247
|
+
requestBody: { values },
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async appendRange(range: string, values: any[][]): Promise<void> {
|
|
253
|
+
const api = await this.getApi();
|
|
254
|
+
await withRetry(() =>
|
|
255
|
+
api.spreadsheets.values.append({
|
|
256
|
+
spreadsheetId: this.config.spreadsheetId,
|
|
257
|
+
range,
|
|
258
|
+
valueInputOption: 'RAW',
|
|
259
|
+
insertDataOption: 'INSERT_ROWS',
|
|
260
|
+
requestBody: { values },
|
|
261
|
+
})
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// ─── Factory ──────────────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
export function createAn5SheetsAdapter(config: An5SheetsAdapterConfig): An5SheetsAdapter {
|
|
268
|
+
return new An5SheetsAdapter(config);
|
|
269
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { An5SheetsAdapter, createAn5SheetsAdapter } from './adapter';
|
|
2
|
-
export { SheetsTableClient } from './tableClient';
|
|
3
|
-
export type { An5SheetsAdapterConfig } from './config';
|
|
1
|
+
export { An5SheetsAdapter, createAn5SheetsAdapter } from './adapter';
|
|
2
|
+
export { SheetsTableClient } from './tableClient';
|
|
3
|
+
export type { An5SheetsAdapterConfig } from './config';
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
// ─── Config ───────────────────────────────────────────────────────────────────
|
|
2
|
-
|
|
3
|
-
export interface An5SheetsAdapterConfig {
|
|
4
|
-
spreadsheetId: string;
|
|
5
|
-
clientEmail?: string;
|
|
6
|
-
privateKey?: string;
|
|
7
|
-
credentials?: { client_email: string; private_key: string };
|
|
8
|
-
sheetMapping?: Record<string, string>;
|
|
9
|
-
/** OAuth2 access token for browser-based authentication (e.g. from Firebase Auth Google sign-in).
|
|
10
|
-
* When provided, the adapter uses raw fetch() instead of googleapis JWT, making it compatible
|
|
11
|
-
* with browser environments. Mutually exclusive with clientEmail/privateKey/credentials. */
|
|
12
|
-
accessToken?: string;
|
|
13
|
-
/** Google API key for browser-based API access. */
|
|
14
|
-
apiKey?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function normalizeKey(key: string): string {
|
|
18
|
-
if (key.includes('PRIVATE KEY')) return key;
|
|
19
|
-
const lines = key.split('\\n').join('\n');
|
|
20
|
-
if (lines.includes('PRIVATE KEY')) return lines;
|
|
21
|
-
return `-----BEGIN PRIVATE KEY-----\n${lines}\n-----END PRIVATE KEY-----`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function resolveConfig(config: An5SheetsAdapterConfig) {
|
|
25
|
-
if (config.accessToken || config.apiKey) {
|
|
26
|
-
return {
|
|
27
|
-
spreadsheetId: config.spreadsheetId,
|
|
28
|
-
accessToken: config.accessToken,
|
|
29
|
-
apiKey: config.apiKey,
|
|
30
|
-
sheetMapping: config.sheetMapping,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
if (config.credentials) {
|
|
34
|
-
return {
|
|
35
|
-
spreadsheetId: config.spreadsheetId,
|
|
36
|
-
clientEmail: config.credentials.client_email,
|
|
37
|
-
privateKey: normalizeKey(config.credentials.private_key),
|
|
38
|
-
sheetMapping: config.sheetMapping,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
spreadsheetId: config.spreadsheetId,
|
|
43
|
-
clientEmail: config.clientEmail!,
|
|
44
|
-
privateKey: normalizeKey(config.privateKey!),
|
|
45
|
-
sheetMapping: config.sheetMapping,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
1
|
+
// ─── Config ───────────────────────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
export interface An5SheetsAdapterConfig {
|
|
4
|
+
spreadsheetId: string;
|
|
5
|
+
clientEmail?: string;
|
|
6
|
+
privateKey?: string;
|
|
7
|
+
credentials?: { client_email: string; private_key: string };
|
|
8
|
+
sheetMapping?: Record<string, string>;
|
|
9
|
+
/** OAuth2 access token for browser-based authentication (e.g. from Firebase Auth Google sign-in).
|
|
10
|
+
* When provided, the adapter uses raw fetch() instead of googleapis JWT, making it compatible
|
|
11
|
+
* with browser environments. Mutually exclusive with clientEmail/privateKey/credentials. */
|
|
12
|
+
accessToken?: string;
|
|
13
|
+
/** Google API key for browser-based API access. */
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function normalizeKey(key: string): string {
|
|
18
|
+
if (key.includes('PRIVATE KEY')) return key;
|
|
19
|
+
const lines = key.split('\\n').join('\n');
|
|
20
|
+
if (lines.includes('PRIVATE KEY')) return lines;
|
|
21
|
+
return `-----BEGIN PRIVATE KEY-----\n${lines}\n-----END PRIVATE KEY-----`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveConfig(config: An5SheetsAdapterConfig) {
|
|
25
|
+
if (config.accessToken || config.apiKey) {
|
|
26
|
+
return {
|
|
27
|
+
spreadsheetId: config.spreadsheetId,
|
|
28
|
+
accessToken: config.accessToken,
|
|
29
|
+
apiKey: config.apiKey,
|
|
30
|
+
sheetMapping: config.sheetMapping,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (config.credentials) {
|
|
34
|
+
return {
|
|
35
|
+
spreadsheetId: config.spreadsheetId,
|
|
36
|
+
clientEmail: config.credentials.client_email,
|
|
37
|
+
privateKey: normalizeKey(config.credentials.private_key),
|
|
38
|
+
sheetMapping: config.sheetMapping,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
spreadsheetId: config.spreadsheetId,
|
|
43
|
+
clientEmail: config.clientEmail!,
|
|
44
|
+
privateKey: normalizeKey(config.privateKey!),
|
|
45
|
+
sheetMapping: config.sheetMapping,
|
|
46
|
+
};
|
|
47
|
+
}
|