@dbml/connector 3.13.9-alpha.0 → 3.14.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.
@@ -4,16 +4,32 @@ exports.fetchSchemaJson = fetchSchemaJson;
4
4
  const bigquery_1 = require("@google-cloud/bigquery");
5
5
  const credential_loader_1 = require("../utils/credential-loader");
6
6
  const helpers_1 = require("../utils/helpers");
7
+ // BigQuery client authentication strategy:
8
+ // 1. Start with environment/default authentication:
9
+ // - GOOGLE_APPLICATION_CREDENTIALS env variable (service account key file)
10
+ // - Application Default Credentials (gcloud auth application-default login)
11
+ //
12
+ // 2. Override with explicitly provided properties:
13
+ // - If projectId is provided, override the default project
14
+ // - If credentials (clientEmail + privateKey) provided, override auth method
15
+ // - If datasets specified in config, fetch only those datasets
7
16
  async function connectBigQuery(credential) {
8
- const client = new bigquery_1.BigQuery({
9
- projectId: credential.projectId,
10
- credentials: {
11
- private_key: credential.credentials.privateKey,
17
+ const config = {};
18
+ // Override project if explicitly provided
19
+ if (credential?.projectId) {
20
+ config.projectId = credential.projectId;
21
+ }
22
+ // Override credentials if both email and key are provided
23
+ if (credential?.credentials?.clientEmail && credential?.credentials?.privateKey) {
24
+ config.credentials = {
12
25
  client_email: credential.credentials.clientEmail,
13
- },
14
- });
26
+ private_key: credential.credentials.privateKey,
27
+ };
28
+ }
29
+ // Create client with merged config
30
+ const client = new bigquery_1.BigQuery(config);
15
31
  try {
16
- const query = `SELECT CURRENT_DATE() as today`;
32
+ const query = 'SELECT CURRENT_DATE() as today';
17
33
  await client.query(query);
18
34
  return client;
19
35
  }
@@ -30,6 +46,7 @@ function getDbDefault(columnDefault, defaultValueType) {
30
46
  }
31
47
  if (defaultValueType === 'string') {
32
48
  // bigquery store the double qoutes and quotes at the end and the beginning of the string: "string" or 'string'
49
+ // eslint-disable-next-line no-useless-escape
33
50
  return { value: columnDefault.slice(1, -1).replaceAll("'", "\'"), type: defaultValueType };
34
51
  }
35
52
  return { value: columnDefault, type: defaultValueType };
@@ -53,17 +70,17 @@ function generateField(row) {
53
70
  return field;
54
71
  }
55
72
  async function generateTablesAndFields(client, projectId, datasetId) {
56
- const tableName = `${projectId}.${datasetId}`;
73
+ const tableNameToFetch = `${projectId}.${datasetId}`;
57
74
  const query = `
58
75
  with
59
76
  table_comments as (
60
77
  select table_schema, table_name, option_value as description
61
- from ${tableName}.INFORMATION_SCHEMA.TABLE_OPTIONS
78
+ from ${tableNameToFetch}.INFORMATION_SCHEMA.TABLE_OPTIONS
62
79
  where option_name = 'description'
63
80
  ),
64
81
  column_comments as (
65
82
  select column_name, table_schema, table_name, description
66
- from ${tableName}.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS
83
+ from ${tableNameToFetch}.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS
67
84
  where column_name = field_path
68
85
  )
69
86
  select
@@ -87,8 +104,8 @@ async function generateTablesAndFields(client, projectId, datasetId) {
87
104
  WHEN c.column_default = "NULL" THEN NULL
88
105
  ELSE 'expression'
89
106
  END AS columnDefaultType
90
- from ${tableName}.INFORMATION_SCHEMA.TABLES t
91
- join ${tableName}.INFORMATION_SCHEMA.COLUMNS c
107
+ from ${tableNameToFetch}.INFORMATION_SCHEMA.TABLES t
108
+ join ${tableNameToFetch}.INFORMATION_SCHEMA.COLUMNS c
92
109
  on
93
110
  t.table_schema = c.table_schema
94
111
  and t.table_name = c.table_name
@@ -120,15 +137,15 @@ async function generateTablesAndFields(client, projectId, datasetId) {
120
137
  };
121
138
  }
122
139
  async function generateIndexes(client, projectId, datasetId) {
123
- const tableName = `${projectId}.${datasetId}`;
140
+ const tableNameToFetch = `${projectId}.${datasetId}`;
124
141
  const query = `
125
142
  select
126
143
  s.index_schema as indexSchema,
127
144
  s.table_name as tableName,
128
145
  s.index_name as indexName,
129
146
  string_agg(sc.index_column_name, ', ') as indexColumnNames
130
- from ${tableName}.INFORMATION_SCHEMA.SEARCH_INDEXES s
131
- join ${tableName}.INFORMATION_SCHEMA.SEARCH_INDEX_COLUMNS sc
147
+ from ${tableNameToFetch}.INFORMATION_SCHEMA.SEARCH_INDEXES s
148
+ join ${tableNameToFetch}.INFORMATION_SCHEMA.SEARCH_INDEX_COLUMNS sc
132
149
  on
133
150
  s.index_schema = sc.index_schema
134
151
  and s.table_name = sc.table_name
@@ -140,7 +157,7 @@ async function generateIndexes(client, projectId, datasetId) {
140
157
  const [queryResult] = await client.query(query);
141
158
  const indexMap = {};
142
159
  queryResult.forEach((row) => {
143
- const { indexSchema, tableName, indexName, indexColumnNames } = row;
160
+ const { indexSchema, tableName, indexName, indexColumnNames, } = row;
144
161
  const indexColumns = indexColumnNames.split(', ').map((column) => {
145
162
  return {
146
163
  type: 'column',
@@ -163,7 +180,7 @@ async function generateIndexes(client, projectId, datasetId) {
163
180
  return indexMap;
164
181
  }
165
182
  async function generateTableConstraints(client, projectId, datasetId) {
166
- const tableName = `${projectId}.${datasetId}`;
183
+ const tableNameToFetch = `${projectId}.${datasetId}`;
167
184
  const query = `
168
185
  select
169
186
  tc.table_schema as tableSchema,
@@ -171,8 +188,8 @@ async function generateTableConstraints(client, projectId, datasetId) {
171
188
  tc.constraint_name as constraintName,
172
189
  string_agg(kcu.column_name, ', ') as columnNames,
173
190
  count(kcu.column_name) as columnCount,
174
- from ${tableName}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
175
- join ${tableName}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
191
+ from ${tableNameToFetch}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
192
+ join ${tableNameToFetch}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
176
193
  on
177
194
  tc.table_schema = kcu.table_schema
178
195
  and tc.table_name = kcu.table_name
@@ -198,7 +215,7 @@ async function generateTableConstraints(client, projectId, datasetId) {
198
215
  });
199
216
  const compositePrimaryKeyMap = {};
200
217
  compositePrimaryKeyList.forEach((compositeKey) => {
201
- const { tableSchema, tableName, constraintName, columnNames } = compositeKey;
218
+ const { tableSchema, tableName, constraintName, columnNames, } = compositeKey;
202
219
  const indexColumns = columnNames.split(', ').map((column) => {
203
220
  return {
204
221
  type: 'column',
@@ -260,12 +277,14 @@ async function fetchSchemaJson(keyFilename) {
260
277
  const querySchemaPromises = datasetIdList.map((dataset) => fetchSchemaJsonByDataset(client, projectId, dataset));
261
278
  const databaseSchemaRes = await Promise.all(querySchemaPromises);
262
279
  const res = databaseSchemaRes.reduce((acc, dbSchema) => {
263
- const { tables, fields, indexes, tableConstraints } = dbSchema;
280
+ const { tables, fields, indexes, tableConstraints, } = dbSchema;
264
281
  acc.tables = (0, helpers_1.mergeTables)(acc.tables, tables);
265
282
  acc.fields = (0, helpers_1.mergeFieldDictionary)(acc.fields, fields);
266
283
  acc.indexes = (0, helpers_1.mergeIndexDictionary)(acc.indexes, indexes);
267
284
  acc.tableConstraints = (0, helpers_1.mergeTableConstraintDictionary)(acc.tableConstraints, tableConstraints);
268
285
  return acc;
269
- }, { tables: [], fields: {}, tableConstraints: {}, indexes: {}, refs: [], enums: [] });
286
+ }, {
287
+ tables: [], fields: {}, tableConstraints: {}, indexes: {}, refs: [], enums: [],
288
+ });
270
289
  return res;
271
290
  }
@@ -80,8 +80,8 @@ interface DatabaseSchema {
80
80
  indexes: IndexesDictionary;
81
81
  }
82
82
  interface BigQueryCredentials {
83
- projectId: string;
84
- credentials: {
83
+ projectId?: string;
84
+ credentials?: {
85
85
  clientEmail: string;
86
86
  privateKey: string;
87
87
  };
@@ -19,30 +19,35 @@ function parseBigQueryCredential(credentialString) {
19
19
  try {
20
20
  const credentialJson = JSON.parse(credentialString);
21
21
  const { project_id: projectId, client_email: clientEmail, private_key: privateKey, datasets, } = credentialJson;
22
- if (typeof projectId !== 'string' || !projectId || !projectId.trim()) {
23
- throw new Error('project_id must be a non-empty string');
24
- }
25
- if (typeof clientEmail !== 'string' || !clientEmail || !clientEmail.trim()) {
26
- throw new Error('client_email must be a non-empty string');
27
- }
28
- if (typeof privateKey !== 'string' || !privateKey || !privateKey.trim()) {
29
- throw new Error('private_key must be a non-empty string');
30
- }
31
- if (typeof clientEmail !== 'string' || !clientEmail || !clientEmail.trim()) {
32
- throw new Error('client_email must be a non-empty string');
33
- }
34
22
  // valid datasets: ['dataset_1', 'dataset_2']
35
23
  const parsedDatasets = !Array.isArray(datasets)
36
24
  ? []
37
25
  : datasets.filter((dataset) => typeof dataset === 'string');
38
- return {
39
- projectId,
40
- credentials: {
41
- clientEmail,
42
- privateKey,
43
- },
26
+ const parsedCredentials = {
44
27
  datasets: parsedDatasets,
45
28
  };
29
+ // Validate and add projectId if provided
30
+ if (projectId !== undefined) {
31
+ if (typeof projectId !== 'string' || !projectId || !projectId.trim()) {
32
+ throw new Error('project_id must be a non-empty string when provided');
33
+ }
34
+ parsedCredentials.projectId = projectId;
35
+ }
36
+ // Validate and add credentials if provided
37
+ // Note: If one credential field is provided, both must be provided
38
+ if (clientEmail !== undefined || privateKey !== undefined) {
39
+ if (!clientEmail || typeof clientEmail !== 'string' || !clientEmail.trim()) {
40
+ throw new Error('client_email must be a non-empty string when credentials are provided');
41
+ }
42
+ if (!privateKey || typeof privateKey !== 'string' || !privateKey.trim()) {
43
+ throw new Error('private_key must be a non-empty string when credentials are provided');
44
+ }
45
+ parsedCredentials.credentials = {
46
+ clientEmail,
47
+ privateKey,
48
+ };
49
+ }
50
+ return parsedCredentials;
46
51
  }
47
52
  catch (error) {
48
53
  if (error instanceof SyntaxError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbml/connector",
3
- "version": "3.13.9-alpha.0",
3
+ "version": "3.14.0",
4
4
  "description": "This package was created to fetch the schema JSON from many kind of databases.",
5
5
  "author": "huy.phung.sw@gmail.com",
6
6
  "license": "MIT",
@@ -44,5 +44,5 @@
44
44
  "engines": {
45
45
  "node": ">=18"
46
46
  },
47
- "gitHead": "330b32307bc949fbcd948455af53995eeb70e17c"
47
+ "gitHead": "a3be772822c217e0b8afa975e90fa54b81d38276"
48
48
  }
@@ -1,4 +1,4 @@
1
- import { BigQuery } from '@google-cloud/bigquery';
1
+ import { BigQuery, BigQueryOptions } from '@google-cloud/bigquery';
2
2
  import { loadCredentialFromFile, parseBigQueryCredential } from '../utils/credential-loader';
3
3
  import {
4
4
  getIntersection,
@@ -21,17 +21,36 @@ import {
21
21
  TableConstraintsDictionary,
22
22
  } from './types';
23
23
 
24
- async function connectBigQuery(credential: BigQueryCredentials): Promise<BigQuery> {
25
- const client = new BigQuery({
26
- projectId: credential.projectId,
27
- credentials: {
28
- private_key: credential.credentials.privateKey,
24
+ // BigQuery client authentication strategy:
25
+ // 1. Start with environment/default authentication:
26
+ // - GOOGLE_APPLICATION_CREDENTIALS env variable (service account key file)
27
+ // - Application Default Credentials (gcloud auth application-default login)
28
+ //
29
+ // 2. Override with explicitly provided properties:
30
+ // - If projectId is provided, override the default project
31
+ // - If credentials (clientEmail + privateKey) provided, override auth method
32
+ // - If datasets specified in config, fetch only those datasets
33
+ async function connectBigQuery (credential: BigQueryCredentials): Promise<BigQuery> {
34
+ const config: BigQueryOptions = {};
35
+
36
+ // Override project if explicitly provided
37
+ if (credential?.projectId) {
38
+ config.projectId = credential.projectId;
39
+ }
40
+
41
+ // Override credentials if both email and key are provided
42
+ if (credential?.credentials?.clientEmail && credential?.credentials?.privateKey) {
43
+ config.credentials = {
29
44
  client_email: credential.credentials.clientEmail,
30
- },
31
- });
45
+ private_key: credential.credentials.privateKey,
46
+ };
47
+ }
48
+
49
+ // Create client with merged config
50
+ const client = new BigQuery(config);
32
51
 
33
52
  try {
34
- const query = `SELECT CURRENT_DATE() as today`;
53
+ const query = 'SELECT CURRENT_DATE() as today';
35
54
  await client.query(query);
36
55
  return client;
37
56
  } catch (error) {
@@ -43,9 +62,9 @@ async function connectBigQuery(credential: BigQueryCredentials): Promise<BigQuer
43
62
  }
44
63
  }
45
64
 
46
- function getDbDefault(
65
+ function getDbDefault (
47
66
  columnDefault: string,
48
- defaultValueType: DefaultType | null
67
+ defaultValueType: DefaultType | null,
49
68
  ): DefaultInfo | null {
50
69
  if (defaultValueType === null) {
51
70
  return null;
@@ -53,13 +72,14 @@ function getDbDefault(
53
72
 
54
73
  if (defaultValueType === 'string') {
55
74
  // bigquery store the double qoutes and quotes at the end and the beginning of the string: "string" or 'string'
56
- return { value: columnDefault.slice(1, -1).replaceAll("'", "\'"), type: defaultValueType }
75
+ // eslint-disable-next-line no-useless-escape
76
+ return { value: columnDefault.slice(1, -1).replaceAll("'", "\'"), type: defaultValueType };
57
77
  }
58
78
 
59
79
  return { value: columnDefault, type: defaultValueType };
60
80
  }
61
81
 
62
- function generateField(row: any): Field {
82
+ function generateField (row: any): Field {
63
83
  const {
64
84
  columnName,
65
85
  isNullable,
@@ -88,26 +108,26 @@ function generateField(row: any): Field {
88
108
  return field;
89
109
  }
90
110
 
91
- async function generateTablesAndFields(
111
+ async function generateTablesAndFields (
92
112
  client: BigQuery,
93
113
  projectId: string,
94
- datasetId: string
114
+ datasetId: string,
95
115
  ): Promise<{
96
116
  tables: Table[];
97
117
  fieldMap: FieldsDictionary;
98
118
  }> {
99
- const tableName = `${projectId}.${datasetId}`;
119
+ const tableNameToFetch = `${projectId}.${datasetId}`;
100
120
 
101
121
  const query = `
102
122
  with
103
123
  table_comments as (
104
124
  select table_schema, table_name, option_value as description
105
- from ${tableName}.INFORMATION_SCHEMA.TABLE_OPTIONS
125
+ from ${tableNameToFetch}.INFORMATION_SCHEMA.TABLE_OPTIONS
106
126
  where option_name = 'description'
107
127
  ),
108
128
  column_comments as (
109
129
  select column_name, table_schema, table_name, description
110
- from ${tableName}.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS
130
+ from ${tableNameToFetch}.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS
111
131
  where column_name = field_path
112
132
  )
113
133
  select
@@ -131,8 +151,8 @@ async function generateTablesAndFields(
131
151
  WHEN c.column_default = "NULL" THEN NULL
132
152
  ELSE 'expression'
133
153
  END AS columnDefaultType
134
- from ${tableName}.INFORMATION_SCHEMA.TABLES t
135
- join ${tableName}.INFORMATION_SCHEMA.COLUMNS c
154
+ from ${tableNameToFetch}.INFORMATION_SCHEMA.TABLES t
155
+ join ${tableNameToFetch}.INFORMATION_SCHEMA.COLUMNS c
136
156
  on
137
157
  t.table_schema = c.table_schema
138
158
  and t.table_name = c.table_name
@@ -172,12 +192,12 @@ async function generateTablesAndFields(
172
192
  };
173
193
  }
174
194
 
175
- async function generateIndexes(
195
+ async function generateIndexes (
176
196
  client: BigQuery,
177
197
  projectId: string,
178
- datasetId: string
198
+ datasetId: string,
179
199
  ): Promise<IndexesDictionary> {
180
- const tableName = `${projectId}.${datasetId}`;
200
+ const tableNameToFetch = `${projectId}.${datasetId}`;
181
201
 
182
202
  const query = `
183
203
  select
@@ -185,8 +205,8 @@ async function generateIndexes(
185
205
  s.table_name as tableName,
186
206
  s.index_name as indexName,
187
207
  string_agg(sc.index_column_name, ', ') as indexColumnNames
188
- from ${tableName}.INFORMATION_SCHEMA.SEARCH_INDEXES s
189
- join ${tableName}.INFORMATION_SCHEMA.SEARCH_INDEX_COLUMNS sc
208
+ from ${tableNameToFetch}.INFORMATION_SCHEMA.SEARCH_INDEXES s
209
+ join ${tableNameToFetch}.INFORMATION_SCHEMA.SEARCH_INDEX_COLUMNS sc
190
210
  on
191
211
  s.index_schema = sc.index_schema
192
212
  and s.table_name = sc.table_name
@@ -201,7 +221,9 @@ async function generateIndexes(
201
221
  const indexMap: IndexesDictionary = {};
202
222
 
203
223
  queryResult.forEach((row) => {
204
- const { indexSchema, tableName, indexName, indexColumnNames } = row;
224
+ const {
225
+ indexSchema, tableName, indexName, indexColumnNames,
226
+ } = row;
205
227
 
206
228
  const indexColumns = indexColumnNames.split(', ').map((column: string) => {
207
229
  return {
@@ -227,15 +249,15 @@ async function generateIndexes(
227
249
  return indexMap;
228
250
  }
229
251
 
230
- async function generateTableConstraints(
252
+ async function generateTableConstraints (
231
253
  client: BigQuery,
232
254
  projectId: string,
233
- datasetId: string
255
+ datasetId: string,
234
256
  ): Promise<{
235
257
  inlinePrimaryKeyMap: TableConstraintsDictionary;
236
258
  compositePrimaryKeyMap: IndexesDictionary;
237
259
  }> {
238
- const tableName = `${projectId}.${datasetId}`;
260
+ const tableNameToFetch = `${projectId}.${datasetId}`;
239
261
 
240
262
  const query = `
241
263
  select
@@ -244,8 +266,8 @@ async function generateTableConstraints(
244
266
  tc.constraint_name as constraintName,
245
267
  string_agg(kcu.column_name, ', ') as columnNames,
246
268
  count(kcu.column_name) as columnCount,
247
- from ${tableName}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
248
- join ${tableName}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
269
+ from ${tableNameToFetch}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
270
+ join ${tableNameToFetch}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
249
271
  on
250
272
  tc.table_schema = kcu.table_schema
251
273
  and tc.table_name = kcu.table_name
@@ -258,10 +280,10 @@ async function generateTableConstraints(
258
280
  const [queryResult] = await client.query(query);
259
281
 
260
282
  const inlinePrimaryKeyList = queryResult.filter(
261
- (row) => row.columnCount && row.columnCount === 1
283
+ (row) => row.columnCount && row.columnCount === 1,
262
284
  );
263
285
  const compositePrimaryKeyList = queryResult.filter(
264
- (row) => row.columnCount && row.columnCount > 1
286
+ (row) => row.columnCount && row.columnCount > 1,
265
287
  );
266
288
 
267
289
  const inlinePrimaryKeyMap: TableConstraintsDictionary = {};
@@ -283,7 +305,9 @@ async function generateTableConstraints(
283
305
 
284
306
  const compositePrimaryKeyMap: IndexesDictionary = {};
285
307
  compositePrimaryKeyList.forEach((compositeKey) => {
286
- const { tableSchema, tableName, constraintName, columnNames } = compositeKey;
308
+ const {
309
+ tableSchema, tableName, constraintName, columnNames,
310
+ } = compositeKey;
287
311
 
288
312
  const indexColumns = columnNames.split(', ').map((column: string) => {
289
313
  return {
@@ -314,7 +338,7 @@ async function generateTableConstraints(
314
338
  };
315
339
  }
316
340
 
317
- async function validateDatasetIdList(client: BigQuery, datasetIdList: string[]): Promise<string[]> {
341
+ async function validateDatasetIdList (client: BigQuery, datasetIdList: string[]): Promise<string[]> {
318
342
  const [datasets] = await client.getDatasets();
319
343
 
320
344
  const rawDatasetIdList = datasets.map((dataset) => dataset.id).filter((id) => id) as string[];
@@ -326,10 +350,10 @@ async function validateDatasetIdList(client: BigQuery, datasetIdList: string[]):
326
350
  return getIntersection(datasetIdList, rawDatasetIdList);
327
351
  }
328
352
 
329
- async function fetchSchemaJsonByDataset(
353
+ async function fetchSchemaJsonByDataset (
330
354
  client: BigQuery,
331
355
  projectId: string,
332
- datasetId: string
356
+ datasetId: string,
333
357
  ): Promise<DatabaseSchema> {
334
358
  const res = await Promise.all([
335
359
  generateTablesAndFields(client, projectId, datasetId),
@@ -351,7 +375,7 @@ async function fetchSchemaJsonByDataset(
351
375
  };
352
376
  }
353
377
 
354
- async function fetchSchemaJson(keyFilename: string): Promise<DatabaseSchema> {
378
+ async function fetchSchemaJson (keyFilename: string): Promise<DatabaseSchema> {
355
379
  const rawCredential = await loadCredentialFromFile(keyFilename);
356
380
  const credentialJson = parseBigQueryCredential(rawCredential);
357
381
  const client = await connectBigQuery(credentialJson);
@@ -359,14 +383,15 @@ async function fetchSchemaJson(keyFilename: string): Promise<DatabaseSchema> {
359
383
  const projectId = await client.getProjectId();
360
384
  const datasetIdList = await validateDatasetIdList(client, credentialJson.datasets);
361
385
 
362
- const querySchemaPromises = datasetIdList.map((dataset) =>
363
- fetchSchemaJsonByDataset(client, projectId, dataset)
364
- );
386
+ const querySchemaPromises = datasetIdList.map((dataset) => fetchSchemaJsonByDataset(client, projectId, dataset));
365
387
 
366
388
  const databaseSchemaRes = await Promise.all(querySchemaPromises);
367
389
 
368
- const res = databaseSchemaRes.reduce((acc, dbSchema) => {
369
- const { tables, fields, indexes, tableConstraints } = dbSchema;
390
+ const res = databaseSchemaRes.reduce(
391
+ (acc, dbSchema) => {
392
+ const {
393
+ tables, fields, indexes, tableConstraints,
394
+ } = dbSchema;
370
395
 
371
396
  acc.tables = mergeTables(acc.tables, tables);
372
397
  acc.fields = mergeFieldDictionary(acc.fields, fields);
@@ -375,7 +400,9 @@ async function fetchSchemaJson(keyFilename: string): Promise<DatabaseSchema> {
375
400
 
376
401
  return acc;
377
402
  },
378
- { tables: [], fields: {}, tableConstraints: {}, indexes: {}, refs: [], enums: [] }
403
+ {
404
+ tables: [], fields: {}, tableConstraints: {}, indexes: {}, refs: [], enums: [],
405
+ },
379
406
  );
380
407
 
381
408
  return res;
@@ -100,8 +100,8 @@ interface DatabaseSchema {
100
100
  }
101
101
 
102
102
  interface BigQueryCredentials {
103
- projectId: string,
104
- credentials: {
103
+ projectId?: string,
104
+ credentials?: {
105
105
  clientEmail: string,
106
106
  privateKey: string,
107
107
  },
@@ -1,7 +1,7 @@
1
1
  import { readFile } from 'fs/promises';
2
2
  import { BigQueryCredentials } from '../connectors/types';
3
3
 
4
- export async function loadCredentialFromFile(filename: string): Promise<string> {
4
+ export async function loadCredentialFromFile (filename: string): Promise<string> {
5
5
  try {
6
6
  const data = await readFile(filename, { encoding: 'utf8' });
7
7
  return data;
@@ -14,7 +14,7 @@ export async function loadCredentialFromFile(filename: string): Promise<string>
14
14
  }
15
15
  }
16
16
 
17
- export function parseBigQueryCredential(credentialString: string): BigQueryCredentials {
17
+ export function parseBigQueryCredential (credentialString: string): BigQueryCredentials {
18
18
  try {
19
19
  const credentialJson = JSON.parse(credentialString);
20
20
 
@@ -25,35 +25,41 @@ export function parseBigQueryCredential(credentialString: string): BigQueryCrede
25
25
  datasets,
26
26
  } = credentialJson;
27
27
 
28
- if (typeof projectId !== 'string' || !projectId || !projectId.trim()) {
29
- throw new Error('project_id must be a non-empty string');
30
- }
28
+ // valid datasets: ['dataset_1', 'dataset_2']
29
+ const parsedDatasets = !Array.isArray(datasets)
30
+ ? []
31
+ : datasets.filter((dataset) => typeof dataset === 'string');
31
32
 
32
- if (typeof clientEmail !== 'string' || !clientEmail || !clientEmail.trim()) {
33
- throw new Error('client_email must be a non-empty string');
34
- }
33
+ const parsedCredentials: BigQueryCredentials = {
34
+ datasets: parsedDatasets,
35
+ };
35
36
 
36
- if (typeof privateKey !== 'string' || !privateKey || !privateKey.trim()) {
37
- throw new Error('private_key must be a non-empty string');
37
+ // Validate and add projectId if provided
38
+ if (projectId !== undefined) {
39
+ if (typeof projectId !== 'string' || !projectId || !projectId.trim()) {
40
+ throw new Error('project_id must be a non-empty string when provided');
41
+ }
42
+ parsedCredentials.projectId = projectId;
38
43
  }
39
44
 
40
- if (typeof clientEmail !== 'string' || !clientEmail || !clientEmail.trim()) {
41
- throw new Error('client_email must be a non-empty string');
42
- }
45
+ // Validate and add credentials if provided
46
+ // Note: If one credential field is provided, both must be provided
47
+ if (clientEmail !== undefined || privateKey !== undefined) {
48
+ if (!clientEmail || typeof clientEmail !== 'string' || !clientEmail.trim()) {
49
+ throw new Error('client_email must be a non-empty string when credentials are provided');
50
+ }
43
51
 
44
- // valid datasets: ['dataset_1', 'dataset_2']
45
- const parsedDatasets = !Array.isArray(datasets)
46
- ? []
47
- : datasets.filter((dataset) => typeof dataset === 'string');
52
+ if (!privateKey || typeof privateKey !== 'string' || !privateKey.trim()) {
53
+ throw new Error('private_key must be a non-empty string when credentials are provided');
54
+ }
48
55
 
49
- return {
50
- projectId,
51
- credentials: {
56
+ parsedCredentials.credentials = {
52
57
  clientEmail,
53
58
  privateKey,
54
- },
55
- datasets: parsedDatasets,
56
- };
59
+ };
60
+ }
61
+
62
+ return parsedCredentials;
57
63
  } catch (error) {
58
64
  if (error instanceof SyntaxError) {
59
65
  throw new Error('Credentials must be in JSON format');