@squadbase/vite-server 0.0.1-build-4 → 0.0.1-build-5
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/dist/index.js +38 -50
- package/dist/main.js +38 -50
- package/dist/types/data-source.d.ts +4 -6
- package/dist/vite-plugin.js +29 -44
- package/package.json +1 -1
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +0 -659
package/dist/index.js
CHANGED
|
@@ -26,29 +26,29 @@ function createPostgreSQLClient(connectionString) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// src/connector-client/env.ts
|
|
29
|
-
function resolveEnvVar(entry, key,
|
|
29
|
+
function resolveEnvVar(entry, key, connectionId) {
|
|
30
30
|
const envVarName = entry.envVars[key];
|
|
31
31
|
if (!envVarName) {
|
|
32
|
-
throw new Error(`
|
|
32
|
+
throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
|
|
33
33
|
}
|
|
34
34
|
const value = process.env[envVarName];
|
|
35
35
|
if (!value) {
|
|
36
|
-
throw new Error(`Environment variable "${envVarName}" (for "${
|
|
36
|
+
throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
|
|
37
37
|
}
|
|
38
38
|
return value;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// src/connector-client/bigquery.ts
|
|
42
|
-
function createBigQueryClient(entry,
|
|
43
|
-
const projectId = resolveEnvVar(entry, "project-id",
|
|
44
|
-
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64",
|
|
42
|
+
function createBigQueryClient(entry, connectionId) {
|
|
43
|
+
const projectId = resolveEnvVar(entry, "project-id", connectionId);
|
|
44
|
+
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64", connectionId);
|
|
45
45
|
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
46
46
|
let gcpCredentials;
|
|
47
47
|
try {
|
|
48
48
|
gcpCredentials = JSON.parse(serviceAccountJson);
|
|
49
49
|
} catch {
|
|
50
50
|
throw new Error(
|
|
51
|
-
`BigQuery service account JSON (decoded from base64) is not valid JSON for
|
|
51
|
+
`BigQuery service account JSON (decoded from base64) is not valid JSON for connection "${connectionId}"`
|
|
52
52
|
);
|
|
53
53
|
}
|
|
54
54
|
return {
|
|
@@ -63,12 +63,12 @@ function createBigQueryClient(entry, slug) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// src/connector-client/snowflake.ts
|
|
66
|
-
function createSnowflakeClient(entry,
|
|
67
|
-
const accountIdentifier = resolveEnvVar(entry, "account",
|
|
68
|
-
const user = resolveEnvVar(entry, "user",
|
|
69
|
-
const role = resolveEnvVar(entry, "role",
|
|
70
|
-
const warehouse = resolveEnvVar(entry, "warehouse",
|
|
71
|
-
const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64",
|
|
66
|
+
function createSnowflakeClient(entry, connectionId) {
|
|
67
|
+
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
68
|
+
const user = resolveEnvVar(entry, "user", connectionId);
|
|
69
|
+
const role = resolveEnvVar(entry, "role", connectionId);
|
|
70
|
+
const warehouse = resolveEnvVar(entry, "warehouse", connectionId);
|
|
71
|
+
const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64", connectionId);
|
|
72
72
|
const privateKey = Buffer.from(privateKeyBase64, "base64").toString("utf-8");
|
|
73
73
|
return {
|
|
74
74
|
async query(sql) {
|
|
@@ -123,53 +123,38 @@ function createConnectorRegistry() {
|
|
|
123
123
|
}
|
|
124
124
|
return connectionsCache;
|
|
125
125
|
}
|
|
126
|
-
async function getClient2(
|
|
127
|
-
if (!connectorSlug) {
|
|
128
|
-
const cacheKey = "__squadbase-db__";
|
|
129
|
-
const cached2 = clientCache.get(cacheKey);
|
|
130
|
-
if (cached2) return cached2;
|
|
131
|
-
const url = process.env.SQUADBASE_POSTGRESQL_URL;
|
|
132
|
-
if (!url) throw new Error("SQUADBASE_POSTGRESQL_URL environment variable is not set");
|
|
133
|
-
const client = createPostgreSQLClient(url);
|
|
134
|
-
clientCache.set(cacheKey, client);
|
|
135
|
-
return client;
|
|
136
|
-
}
|
|
137
|
-
const cached = clientCache.get(connectorSlug);
|
|
138
|
-
if (cached) return cached;
|
|
126
|
+
async function getClient2(connectionId) {
|
|
139
127
|
const connections = loadConnections();
|
|
140
|
-
const entry = connections[
|
|
128
|
+
const entry = connections[connectionId];
|
|
141
129
|
if (!entry) {
|
|
142
|
-
throw new Error(`
|
|
143
|
-
}
|
|
144
|
-
const resolvedType = connectorType ?? entry.connectorType;
|
|
145
|
-
if (!resolvedType) {
|
|
146
|
-
throw new Error(
|
|
147
|
-
`connector type could not be determined for slug '${connectorSlug}'. Specify connectorType in the data-source JSON or in .squadbase/connections.json.`
|
|
148
|
-
);
|
|
130
|
+
throw new Error(`connection '${connectionId}' not found in .squadbase/connections.json`);
|
|
149
131
|
}
|
|
150
|
-
|
|
151
|
-
|
|
132
|
+
const connectorSlug = entry.connector.slug;
|
|
133
|
+
const cached = clientCache.get(connectionId);
|
|
134
|
+
if (cached) return { client: cached, connectorSlug };
|
|
135
|
+
if (connectorSlug === "snowflake") {
|
|
136
|
+
return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
|
|
152
137
|
}
|
|
153
|
-
if (
|
|
154
|
-
return createBigQueryClient(entry, connectorSlug
|
|
138
|
+
if (connectorSlug === "bigquery") {
|
|
139
|
+
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
155
140
|
}
|
|
156
|
-
if (
|
|
141
|
+
if (connectorSlug === "postgresql" || connectorSlug === "squadbase-db") {
|
|
157
142
|
const urlEnvName = entry.envVars["connection-url"];
|
|
158
143
|
if (!urlEnvName) {
|
|
159
|
-
throw new Error(`'connection-url' is not defined in envVars for
|
|
144
|
+
throw new Error(`'connection-url' is not defined in envVars for connection '${connectionId}'`);
|
|
160
145
|
}
|
|
161
146
|
const connectionUrl = process.env[urlEnvName];
|
|
162
147
|
if (!connectionUrl) {
|
|
163
148
|
throw new Error(
|
|
164
|
-
`environment variable '${urlEnvName}' (mapped from
|
|
149
|
+
`environment variable '${urlEnvName}' (mapped from connection '${connectionId}') is not set`
|
|
165
150
|
);
|
|
166
151
|
}
|
|
167
152
|
const client = createPostgreSQLClient(connectionUrl);
|
|
168
|
-
clientCache.set(
|
|
169
|
-
return client;
|
|
153
|
+
clientCache.set(connectionId, client);
|
|
154
|
+
return { client, connectorSlug };
|
|
170
155
|
}
|
|
171
156
|
throw new Error(
|
|
172
|
-
`connector type '${
|
|
157
|
+
`connector type '${connectorSlug}' is not supported. Supported: "snowflake", "bigquery", "postgresql", "squadbase-db"`
|
|
173
158
|
);
|
|
174
159
|
}
|
|
175
160
|
function reloadEnvFile2(envPath) {
|
|
@@ -277,6 +262,10 @@ async function initialize() {
|
|
|
277
262
|
console.warn(`[registry] Skipping ${file}: missing description`);
|
|
278
263
|
return;
|
|
279
264
|
}
|
|
265
|
+
if (!def.connectionId) {
|
|
266
|
+
console.warn(`[registry] Skipping ${file}: missing connectionId`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
280
269
|
if (def.type === "typescript") {
|
|
281
270
|
if (!def.handlerPath) {
|
|
282
271
|
console.warn(`[registry] Skipping ${file}: missing handlerPath`);
|
|
@@ -287,6 +276,7 @@ async function initialize() {
|
|
|
287
276
|
description: def.description,
|
|
288
277
|
parameters: def.parameters ?? [],
|
|
289
278
|
response: def.response,
|
|
279
|
+
connectionId: def.connectionId,
|
|
290
280
|
cacheConfig: def.cache,
|
|
291
281
|
handler: async () => {
|
|
292
282
|
throw new Error("TypeScript handler must be called via _tsHandlerPath");
|
|
@@ -306,13 +296,12 @@ async function initialize() {
|
|
|
306
296
|
description: sqlDef.description,
|
|
307
297
|
parameters: sqlDef.parameters ?? [],
|
|
308
298
|
response: sqlDef.response,
|
|
309
|
-
|
|
299
|
+
connectionId: sqlDef.connectionId,
|
|
310
300
|
cacheConfig: sqlDef.cache,
|
|
311
301
|
_query: sqlDef.query,
|
|
312
|
-
_connectorType: sqlDef.connectorType,
|
|
313
302
|
handler: async (runtimeParams) => {
|
|
314
|
-
const client = await getClient(sqlDef.
|
|
315
|
-
const isExternalConnector =
|
|
303
|
+
const { client, connectorSlug } = await getClient(sqlDef.connectionId);
|
|
304
|
+
const isExternalConnector = connectorSlug === "snowflake" || connectorSlug === "bigquery";
|
|
316
305
|
let queryText;
|
|
317
306
|
let queryValues;
|
|
318
307
|
if (isExternalConnector) {
|
|
@@ -391,8 +380,7 @@ function buildMeta(slug, def) {
|
|
|
391
380
|
parameters: def.parameters,
|
|
392
381
|
response: def.response,
|
|
393
382
|
query: def._query,
|
|
394
|
-
|
|
395
|
-
connectorSlug: def.connectorSlug,
|
|
383
|
+
connectionId: def.connectionId,
|
|
396
384
|
handlerPath: def._tsHandlerPath ? path2.relative(currentDirPath, def._tsHandlerPath) : void 0,
|
|
397
385
|
cache: def.cacheConfig
|
|
398
386
|
};
|
package/dist/main.js
CHANGED
|
@@ -26,29 +26,29 @@ function createPostgreSQLClient(connectionString) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// src/connector-client/env.ts
|
|
29
|
-
function resolveEnvVar(entry, key,
|
|
29
|
+
function resolveEnvVar(entry, key, connectionId) {
|
|
30
30
|
const envVarName = entry.envVars[key];
|
|
31
31
|
if (!envVarName) {
|
|
32
|
-
throw new Error(`
|
|
32
|
+
throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
|
|
33
33
|
}
|
|
34
34
|
const value = process.env[envVarName];
|
|
35
35
|
if (!value) {
|
|
36
|
-
throw new Error(`Environment variable "${envVarName}" (for "${
|
|
36
|
+
throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
|
|
37
37
|
}
|
|
38
38
|
return value;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// src/connector-client/bigquery.ts
|
|
42
|
-
function createBigQueryClient(entry,
|
|
43
|
-
const projectId = resolveEnvVar(entry, "project-id",
|
|
44
|
-
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64",
|
|
42
|
+
function createBigQueryClient(entry, connectionId) {
|
|
43
|
+
const projectId = resolveEnvVar(entry, "project-id", connectionId);
|
|
44
|
+
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64", connectionId);
|
|
45
45
|
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
46
46
|
let gcpCredentials;
|
|
47
47
|
try {
|
|
48
48
|
gcpCredentials = JSON.parse(serviceAccountJson);
|
|
49
49
|
} catch {
|
|
50
50
|
throw new Error(
|
|
51
|
-
`BigQuery service account JSON (decoded from base64) is not valid JSON for
|
|
51
|
+
`BigQuery service account JSON (decoded from base64) is not valid JSON for connection "${connectionId}"`
|
|
52
52
|
);
|
|
53
53
|
}
|
|
54
54
|
return {
|
|
@@ -63,12 +63,12 @@ function createBigQueryClient(entry, slug) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// src/connector-client/snowflake.ts
|
|
66
|
-
function createSnowflakeClient(entry,
|
|
67
|
-
const accountIdentifier = resolveEnvVar(entry, "account",
|
|
68
|
-
const user = resolveEnvVar(entry, "user",
|
|
69
|
-
const role = resolveEnvVar(entry, "role",
|
|
70
|
-
const warehouse = resolveEnvVar(entry, "warehouse",
|
|
71
|
-
const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64",
|
|
66
|
+
function createSnowflakeClient(entry, connectionId) {
|
|
67
|
+
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
68
|
+
const user = resolveEnvVar(entry, "user", connectionId);
|
|
69
|
+
const role = resolveEnvVar(entry, "role", connectionId);
|
|
70
|
+
const warehouse = resolveEnvVar(entry, "warehouse", connectionId);
|
|
71
|
+
const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64", connectionId);
|
|
72
72
|
const privateKey = Buffer.from(privateKeyBase64, "base64").toString("utf-8");
|
|
73
73
|
return {
|
|
74
74
|
async query(sql) {
|
|
@@ -123,53 +123,38 @@ function createConnectorRegistry() {
|
|
|
123
123
|
}
|
|
124
124
|
return connectionsCache;
|
|
125
125
|
}
|
|
126
|
-
async function getClient2(
|
|
127
|
-
if (!connectorSlug) {
|
|
128
|
-
const cacheKey = "__squadbase-db__";
|
|
129
|
-
const cached2 = clientCache.get(cacheKey);
|
|
130
|
-
if (cached2) return cached2;
|
|
131
|
-
const url = process.env.SQUADBASE_POSTGRESQL_URL;
|
|
132
|
-
if (!url) throw new Error("SQUADBASE_POSTGRESQL_URL environment variable is not set");
|
|
133
|
-
const client = createPostgreSQLClient(url);
|
|
134
|
-
clientCache.set(cacheKey, client);
|
|
135
|
-
return client;
|
|
136
|
-
}
|
|
137
|
-
const cached = clientCache.get(connectorSlug);
|
|
138
|
-
if (cached) return cached;
|
|
126
|
+
async function getClient2(connectionId) {
|
|
139
127
|
const connections = loadConnections();
|
|
140
|
-
const entry = connections[
|
|
128
|
+
const entry = connections[connectionId];
|
|
141
129
|
if (!entry) {
|
|
142
|
-
throw new Error(`
|
|
143
|
-
}
|
|
144
|
-
const resolvedType = connectorType ?? entry.connectorType;
|
|
145
|
-
if (!resolvedType) {
|
|
146
|
-
throw new Error(
|
|
147
|
-
`connector type could not be determined for slug '${connectorSlug}'. Specify connectorType in the data-source JSON or in .squadbase/connections.json.`
|
|
148
|
-
);
|
|
130
|
+
throw new Error(`connection '${connectionId}' not found in .squadbase/connections.json`);
|
|
149
131
|
}
|
|
150
|
-
|
|
151
|
-
|
|
132
|
+
const connectorSlug = entry.connector.slug;
|
|
133
|
+
const cached = clientCache.get(connectionId);
|
|
134
|
+
if (cached) return { client: cached, connectorSlug };
|
|
135
|
+
if (connectorSlug === "snowflake") {
|
|
136
|
+
return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
|
|
152
137
|
}
|
|
153
|
-
if (
|
|
154
|
-
return createBigQueryClient(entry, connectorSlug
|
|
138
|
+
if (connectorSlug === "bigquery") {
|
|
139
|
+
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
155
140
|
}
|
|
156
|
-
if (
|
|
141
|
+
if (connectorSlug === "postgresql" || connectorSlug === "squadbase-db") {
|
|
157
142
|
const urlEnvName = entry.envVars["connection-url"];
|
|
158
143
|
if (!urlEnvName) {
|
|
159
|
-
throw new Error(`'connection-url' is not defined in envVars for
|
|
144
|
+
throw new Error(`'connection-url' is not defined in envVars for connection '${connectionId}'`);
|
|
160
145
|
}
|
|
161
146
|
const connectionUrl = process.env[urlEnvName];
|
|
162
147
|
if (!connectionUrl) {
|
|
163
148
|
throw new Error(
|
|
164
|
-
`environment variable '${urlEnvName}' (mapped from
|
|
149
|
+
`environment variable '${urlEnvName}' (mapped from connection '${connectionId}') is not set`
|
|
165
150
|
);
|
|
166
151
|
}
|
|
167
152
|
const client = createPostgreSQLClient(connectionUrl);
|
|
168
|
-
clientCache.set(
|
|
169
|
-
return client;
|
|
153
|
+
clientCache.set(connectionId, client);
|
|
154
|
+
return { client, connectorSlug };
|
|
170
155
|
}
|
|
171
156
|
throw new Error(
|
|
172
|
-
`connector type '${
|
|
157
|
+
`connector type '${connectorSlug}' is not supported. Supported: "snowflake", "bigquery", "postgresql", "squadbase-db"`
|
|
173
158
|
);
|
|
174
159
|
}
|
|
175
160
|
function reloadEnvFile2(envPath) {
|
|
@@ -277,6 +262,10 @@ async function initialize() {
|
|
|
277
262
|
console.warn(`[registry] Skipping ${file}: missing description`);
|
|
278
263
|
return;
|
|
279
264
|
}
|
|
265
|
+
if (!def.connectionId) {
|
|
266
|
+
console.warn(`[registry] Skipping ${file}: missing connectionId`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
280
269
|
if (def.type === "typescript") {
|
|
281
270
|
if (!def.handlerPath) {
|
|
282
271
|
console.warn(`[registry] Skipping ${file}: missing handlerPath`);
|
|
@@ -287,6 +276,7 @@ async function initialize() {
|
|
|
287
276
|
description: def.description,
|
|
288
277
|
parameters: def.parameters ?? [],
|
|
289
278
|
response: def.response,
|
|
279
|
+
connectionId: def.connectionId,
|
|
290
280
|
cacheConfig: def.cache,
|
|
291
281
|
handler: async () => {
|
|
292
282
|
throw new Error("TypeScript handler must be called via _tsHandlerPath");
|
|
@@ -306,13 +296,12 @@ async function initialize() {
|
|
|
306
296
|
description: sqlDef.description,
|
|
307
297
|
parameters: sqlDef.parameters ?? [],
|
|
308
298
|
response: sqlDef.response,
|
|
309
|
-
|
|
299
|
+
connectionId: sqlDef.connectionId,
|
|
310
300
|
cacheConfig: sqlDef.cache,
|
|
311
301
|
_query: sqlDef.query,
|
|
312
|
-
_connectorType: sqlDef.connectorType,
|
|
313
302
|
handler: async (runtimeParams) => {
|
|
314
|
-
const client = await getClient(sqlDef.
|
|
315
|
-
const isExternalConnector =
|
|
303
|
+
const { client, connectorSlug } = await getClient(sqlDef.connectionId);
|
|
304
|
+
const isExternalConnector = connectorSlug === "snowflake" || connectorSlug === "bigquery";
|
|
316
305
|
let queryText;
|
|
317
306
|
let queryValues;
|
|
318
307
|
if (isExternalConnector) {
|
|
@@ -391,8 +380,7 @@ function buildMeta(slug, def) {
|
|
|
391
380
|
parameters: def.parameters,
|
|
392
381
|
response: def.response,
|
|
393
382
|
query: def._query,
|
|
394
|
-
|
|
395
|
-
connectorSlug: def.connectorSlug,
|
|
383
|
+
connectionId: def.connectionId,
|
|
396
384
|
handlerPath: def._tsHandlerPath ? path2.relative(currentDirPath, def._tsHandlerPath) : void 0,
|
|
397
385
|
cache: def.cacheConfig
|
|
398
386
|
};
|
|
@@ -47,13 +47,12 @@ interface DataSourceDefinition {
|
|
|
47
47
|
description: string;
|
|
48
48
|
parameters: ParameterMeta[];
|
|
49
49
|
response?: DataSourceResponse;
|
|
50
|
-
|
|
50
|
+
connectionId: string;
|
|
51
51
|
cacheConfig?: DataSourceCacheConfig;
|
|
52
52
|
handler: (params: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
53
53
|
_isTypescript?: boolean;
|
|
54
54
|
_tsHandlerPath?: string;
|
|
55
55
|
_query?: string;
|
|
56
|
-
_connectorType?: string;
|
|
57
56
|
}
|
|
58
57
|
interface DataSourceMeta {
|
|
59
58
|
slug: string;
|
|
@@ -62,8 +61,7 @@ interface DataSourceMeta {
|
|
|
62
61
|
parameters: ParameterMeta[];
|
|
63
62
|
response?: DataSourceResponse;
|
|
64
63
|
query?: string;
|
|
65
|
-
|
|
66
|
-
connectorSlug?: string;
|
|
64
|
+
connectionId: string;
|
|
67
65
|
handlerPath?: string;
|
|
68
66
|
cache?: DataSourceCacheConfig;
|
|
69
67
|
}
|
|
@@ -73,14 +71,14 @@ interface JsonDataSourceDefinition {
|
|
|
73
71
|
parameters?: ParameterMeta[];
|
|
74
72
|
response?: DataSourceResponse;
|
|
75
73
|
query: string;
|
|
76
|
-
|
|
77
|
-
connectorSlug?: string;
|
|
74
|
+
connectionId: string;
|
|
78
75
|
cache?: DataSourceCacheConfig;
|
|
79
76
|
}
|
|
80
77
|
interface JsonTypeScriptDataSourceDefinition {
|
|
81
78
|
description: string;
|
|
82
79
|
type: "typescript";
|
|
83
80
|
handlerPath: string;
|
|
81
|
+
connectionId: string;
|
|
84
82
|
parameters?: ParameterMeta[];
|
|
85
83
|
response?: DataSourceResponse;
|
|
86
84
|
cache?: DataSourceCacheConfig;
|
package/dist/vite-plugin.js
CHANGED
|
@@ -26,29 +26,29 @@ function createPostgreSQLClient(connectionString) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// src/connector-client/env.ts
|
|
29
|
-
function resolveEnvVar(entry, key,
|
|
29
|
+
function resolveEnvVar(entry, key, connectionId) {
|
|
30
30
|
const envVarName = entry.envVars[key];
|
|
31
31
|
if (!envVarName) {
|
|
32
|
-
throw new Error(`
|
|
32
|
+
throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
|
|
33
33
|
}
|
|
34
34
|
const value = process.env[envVarName];
|
|
35
35
|
if (!value) {
|
|
36
|
-
throw new Error(`Environment variable "${envVarName}" (for "${
|
|
36
|
+
throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
|
|
37
37
|
}
|
|
38
38
|
return value;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// src/connector-client/bigquery.ts
|
|
42
|
-
function createBigQueryClient(entry,
|
|
43
|
-
const projectId = resolveEnvVar(entry, "project-id",
|
|
44
|
-
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64",
|
|
42
|
+
function createBigQueryClient(entry, connectionId) {
|
|
43
|
+
const projectId = resolveEnvVar(entry, "project-id", connectionId);
|
|
44
|
+
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64", connectionId);
|
|
45
45
|
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
46
46
|
let gcpCredentials;
|
|
47
47
|
try {
|
|
48
48
|
gcpCredentials = JSON.parse(serviceAccountJson);
|
|
49
49
|
} catch {
|
|
50
50
|
throw new Error(
|
|
51
|
-
`BigQuery service account JSON (decoded from base64) is not valid JSON for
|
|
51
|
+
`BigQuery service account JSON (decoded from base64) is not valid JSON for connection "${connectionId}"`
|
|
52
52
|
);
|
|
53
53
|
}
|
|
54
54
|
return {
|
|
@@ -63,12 +63,12 @@ function createBigQueryClient(entry, slug) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// src/connector-client/snowflake.ts
|
|
66
|
-
function createSnowflakeClient(entry,
|
|
67
|
-
const accountIdentifier = resolveEnvVar(entry, "account",
|
|
68
|
-
const user = resolveEnvVar(entry, "user",
|
|
69
|
-
const role = resolveEnvVar(entry, "role",
|
|
70
|
-
const warehouse = resolveEnvVar(entry, "warehouse",
|
|
71
|
-
const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64",
|
|
66
|
+
function createSnowflakeClient(entry, connectionId) {
|
|
67
|
+
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
68
|
+
const user = resolveEnvVar(entry, "user", connectionId);
|
|
69
|
+
const role = resolveEnvVar(entry, "role", connectionId);
|
|
70
|
+
const warehouse = resolveEnvVar(entry, "warehouse", connectionId);
|
|
71
|
+
const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64", connectionId);
|
|
72
72
|
const privateKey = Buffer.from(privateKeyBase64, "base64").toString("utf-8");
|
|
73
73
|
return {
|
|
74
74
|
async query(sql) {
|
|
@@ -123,53 +123,38 @@ function createConnectorRegistry() {
|
|
|
123
123
|
}
|
|
124
124
|
return connectionsCache;
|
|
125
125
|
}
|
|
126
|
-
async function getClient2(
|
|
127
|
-
if (!connectorSlug) {
|
|
128
|
-
const cacheKey = "__squadbase-db__";
|
|
129
|
-
const cached2 = clientCache.get(cacheKey);
|
|
130
|
-
if (cached2) return cached2;
|
|
131
|
-
const url = process.env.SQUADBASE_POSTGRESQL_URL;
|
|
132
|
-
if (!url) throw new Error("SQUADBASE_POSTGRESQL_URL environment variable is not set");
|
|
133
|
-
const client = createPostgreSQLClient(url);
|
|
134
|
-
clientCache.set(cacheKey, client);
|
|
135
|
-
return client;
|
|
136
|
-
}
|
|
137
|
-
const cached = clientCache.get(connectorSlug);
|
|
138
|
-
if (cached) return cached;
|
|
126
|
+
async function getClient2(connectionId) {
|
|
139
127
|
const connections = loadConnections();
|
|
140
|
-
const entry = connections[
|
|
128
|
+
const entry = connections[connectionId];
|
|
141
129
|
if (!entry) {
|
|
142
|
-
throw new Error(`
|
|
143
|
-
}
|
|
144
|
-
const resolvedType = connectorType ?? entry.connectorType;
|
|
145
|
-
if (!resolvedType) {
|
|
146
|
-
throw new Error(
|
|
147
|
-
`connector type could not be determined for slug '${connectorSlug}'. Specify connectorType in the data-source JSON or in .squadbase/connections.json.`
|
|
148
|
-
);
|
|
130
|
+
throw new Error(`connection '${connectionId}' not found in .squadbase/connections.json`);
|
|
149
131
|
}
|
|
150
|
-
|
|
151
|
-
|
|
132
|
+
const connectorSlug = entry.connector.slug;
|
|
133
|
+
const cached = clientCache.get(connectionId);
|
|
134
|
+
if (cached) return { client: cached, connectorSlug };
|
|
135
|
+
if (connectorSlug === "snowflake") {
|
|
136
|
+
return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
|
|
152
137
|
}
|
|
153
|
-
if (
|
|
154
|
-
return createBigQueryClient(entry, connectorSlug
|
|
138
|
+
if (connectorSlug === "bigquery") {
|
|
139
|
+
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
155
140
|
}
|
|
156
|
-
if (
|
|
141
|
+
if (connectorSlug === "postgresql" || connectorSlug === "squadbase-db") {
|
|
157
142
|
const urlEnvName = entry.envVars["connection-url"];
|
|
158
143
|
if (!urlEnvName) {
|
|
159
|
-
throw new Error(`'connection-url' is not defined in envVars for
|
|
144
|
+
throw new Error(`'connection-url' is not defined in envVars for connection '${connectionId}'`);
|
|
160
145
|
}
|
|
161
146
|
const connectionUrl = process.env[urlEnvName];
|
|
162
147
|
if (!connectionUrl) {
|
|
163
148
|
throw new Error(
|
|
164
|
-
`environment variable '${urlEnvName}' (mapped from
|
|
149
|
+
`environment variable '${urlEnvName}' (mapped from connection '${connectionId}') is not set`
|
|
165
150
|
);
|
|
166
151
|
}
|
|
167
152
|
const client = createPostgreSQLClient(connectionUrl);
|
|
168
|
-
clientCache.set(
|
|
169
|
-
return client;
|
|
153
|
+
clientCache.set(connectionId, client);
|
|
154
|
+
return { client, connectorSlug };
|
|
170
155
|
}
|
|
171
156
|
throw new Error(
|
|
172
|
-
`connector type '${
|
|
157
|
+
`connector type '${connectorSlug}' is not supported. Supported: "snowflake", "bigquery", "postgresql", "squadbase-db"`
|
|
173
158
|
);
|
|
174
159
|
}
|
|
175
160
|
function reloadEnvFile2(envPath) {
|
package/package.json
CHANGED
package/dist/cli/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
package/dist/cli/index.js
DELETED
|
@@ -1,659 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __esm = (fn, res) => function __init() {
|
|
5
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
-
};
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
// src/cli/interactive.ts
|
|
13
|
-
var interactive_exports = {};
|
|
14
|
-
__export(interactive_exports, {
|
|
15
|
-
confirmRunAll: () => confirmRunAll,
|
|
16
|
-
inputParameters: () => inputParameters,
|
|
17
|
-
selectDataSource: () => selectDataSource
|
|
18
|
-
});
|
|
19
|
-
async function getPrompts() {
|
|
20
|
-
try {
|
|
21
|
-
const mod = await import("@clack/prompts");
|
|
22
|
-
return mod;
|
|
23
|
-
} catch {
|
|
24
|
-
throw new Error(
|
|
25
|
-
"@clack/prompts is not installed. Run: npm install @clack/prompts --save-dev"
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
async function selectDataSource(slugs) {
|
|
30
|
-
const { select, isCancel } = await getPrompts();
|
|
31
|
-
const result = await select({
|
|
32
|
-
message: "Select a data source to test:",
|
|
33
|
-
options: slugs.map((s) => ({ value: s, label: s }))
|
|
34
|
-
});
|
|
35
|
-
if (isCancel(result)) return null;
|
|
36
|
-
return result;
|
|
37
|
-
}
|
|
38
|
-
async function inputParameters(params) {
|
|
39
|
-
if (params.length === 0) return {};
|
|
40
|
-
const { text, isCancel } = await getPrompts();
|
|
41
|
-
const result = {};
|
|
42
|
-
for (const p of params) {
|
|
43
|
-
const defaultHint = p.default !== void 0 ? ` (default: ${p.default})` : "";
|
|
44
|
-
const requiredHint = p.required ? " *required*" : "";
|
|
45
|
-
const value = await text({
|
|
46
|
-
message: `${p.name}${requiredHint} [${p.type}]${defaultHint}: ${p.description}`,
|
|
47
|
-
placeholder: p.default !== void 0 ? String(p.default) : "",
|
|
48
|
-
validate: (v) => {
|
|
49
|
-
if (p.required && !v && p.default === void 0) {
|
|
50
|
-
return `${p.name} is required`;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
if (isCancel(value)) {
|
|
55
|
-
process.exit(0);
|
|
56
|
-
}
|
|
57
|
-
const strValue = value;
|
|
58
|
-
if (!strValue && p.default !== void 0) {
|
|
59
|
-
result[p.name] = p.default;
|
|
60
|
-
} else if (strValue) {
|
|
61
|
-
if (p.type === "number") result[p.name] = Number(strValue);
|
|
62
|
-
else if (p.type === "boolean") result[p.name] = strValue === "true";
|
|
63
|
-
else result[p.name] = strValue;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
68
|
-
async function confirmRunAll() {
|
|
69
|
-
const { confirm, isCancel } = await getPrompts();
|
|
70
|
-
const result = await confirm({
|
|
71
|
-
message: "Run all data sources?"
|
|
72
|
-
});
|
|
73
|
-
if (isCancel(result)) return false;
|
|
74
|
-
return result;
|
|
75
|
-
}
|
|
76
|
-
var init_interactive = __esm({
|
|
77
|
-
"src/cli/interactive.ts"() {
|
|
78
|
-
"use strict";
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// src/cli/index.ts
|
|
83
|
-
import { parseArgs } from "util";
|
|
84
|
-
import path4 from "path";
|
|
85
|
-
import { readFile as readFile3 } from "fs/promises";
|
|
86
|
-
|
|
87
|
-
// src/connector-client/registry.ts
|
|
88
|
-
import { readFileSync, watch as fsWatch } from "fs";
|
|
89
|
-
import path from "path";
|
|
90
|
-
|
|
91
|
-
// src/connector-client/postgresql.ts
|
|
92
|
-
import pg from "pg";
|
|
93
|
-
var { Pool } = pg;
|
|
94
|
-
function createPostgreSQLClient(connectionString) {
|
|
95
|
-
const pool = new Pool({ connectionString, ssl: { rejectUnauthorized: false } });
|
|
96
|
-
return {
|
|
97
|
-
async query(sql, params) {
|
|
98
|
-
const result = await pool.query(sql, params);
|
|
99
|
-
return { rows: result.rows };
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// src/connector-client/env.ts
|
|
105
|
-
function resolveEnvVar(entry, key, slug) {
|
|
106
|
-
const envVarName = entry.envVars[key];
|
|
107
|
-
if (!envVarName) {
|
|
108
|
-
throw new Error(`Connector "${slug}" is missing envVars mapping for key "${key}"`);
|
|
109
|
-
}
|
|
110
|
-
const value = process.env[envVarName];
|
|
111
|
-
if (!value) {
|
|
112
|
-
throw new Error(`Environment variable "${envVarName}" (for "${slug}.${key}") is not set`);
|
|
113
|
-
}
|
|
114
|
-
return value;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// src/connector-client/bigquery.ts
|
|
118
|
-
function createBigQueryClient(entry, slug) {
|
|
119
|
-
const projectId = resolveEnvVar(entry, "project-id", slug);
|
|
120
|
-
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64", slug);
|
|
121
|
-
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
122
|
-
let gcpCredentials;
|
|
123
|
-
try {
|
|
124
|
-
gcpCredentials = JSON.parse(serviceAccountJson);
|
|
125
|
-
} catch {
|
|
126
|
-
throw new Error(
|
|
127
|
-
`BigQuery service account JSON (decoded from base64) is not valid JSON for slug "${slug}"`
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
return {
|
|
131
|
-
async query(sql) {
|
|
132
|
-
const { BigQuery } = await import("@google-cloud/bigquery");
|
|
133
|
-
const bq = new BigQuery({ projectId, credentials: gcpCredentials });
|
|
134
|
-
const [job] = await bq.createQueryJob({ query: sql });
|
|
135
|
-
const [allRows] = await job.getQueryResults({ timeoutMs: 3e4 });
|
|
136
|
-
return { rows: allRows };
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// src/connector-client/snowflake.ts
|
|
142
|
-
function createSnowflakeClient(entry, slug) {
|
|
143
|
-
const accountIdentifier = resolveEnvVar(entry, "account", slug);
|
|
144
|
-
const user = resolveEnvVar(entry, "user", slug);
|
|
145
|
-
const role = resolveEnvVar(entry, "role", slug);
|
|
146
|
-
const warehouse = resolveEnvVar(entry, "warehouse", slug);
|
|
147
|
-
const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64", slug);
|
|
148
|
-
const privateKey = Buffer.from(privateKeyBase64, "base64").toString("utf-8");
|
|
149
|
-
return {
|
|
150
|
-
async query(sql) {
|
|
151
|
-
const snowflake = (await import("snowflake-sdk")).default;
|
|
152
|
-
snowflake.configure({ logLevel: "ERROR" });
|
|
153
|
-
const connection = snowflake.createConnection({
|
|
154
|
-
account: accountIdentifier,
|
|
155
|
-
username: user,
|
|
156
|
-
role,
|
|
157
|
-
warehouse,
|
|
158
|
-
authenticator: "SNOWFLAKE_JWT",
|
|
159
|
-
privateKey
|
|
160
|
-
});
|
|
161
|
-
await new Promise((resolve, reject) => {
|
|
162
|
-
connection.connect((err) => {
|
|
163
|
-
if (err) reject(new Error(`Snowflake connect failed: ${err.message}`));
|
|
164
|
-
else resolve();
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
const rows = await new Promise((resolve, reject) => {
|
|
168
|
-
connection.execute({
|
|
169
|
-
sqlText: sql,
|
|
170
|
-
complete: (err, _stmt, rows2) => {
|
|
171
|
-
if (err) reject(new Error(`Snowflake query failed: ${err.message}`));
|
|
172
|
-
else resolve(rows2 ?? []);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
connection.destroy((err) => {
|
|
177
|
-
if (err) console.warn(`[connector-client] Snowflake destroy error: ${err.message}`);
|
|
178
|
-
});
|
|
179
|
-
return { rows };
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// src/connector-client/registry.ts
|
|
185
|
-
function createConnectorRegistry() {
|
|
186
|
-
let connectionsCache = null;
|
|
187
|
-
const clientCache = /* @__PURE__ */ new Map();
|
|
188
|
-
function getConnectionsFilePath() {
|
|
189
|
-
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), "../../.squadbase/connections.json");
|
|
190
|
-
}
|
|
191
|
-
function loadConnections() {
|
|
192
|
-
if (connectionsCache !== null) return connectionsCache;
|
|
193
|
-
const filePath = getConnectionsFilePath();
|
|
194
|
-
try {
|
|
195
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
196
|
-
connectionsCache = JSON.parse(raw);
|
|
197
|
-
} catch {
|
|
198
|
-
connectionsCache = {};
|
|
199
|
-
}
|
|
200
|
-
return connectionsCache;
|
|
201
|
-
}
|
|
202
|
-
async function getClient2(connectorSlug, connectorType) {
|
|
203
|
-
if (!connectorSlug) {
|
|
204
|
-
const cacheKey = "__squadbase-db__";
|
|
205
|
-
const cached2 = clientCache.get(cacheKey);
|
|
206
|
-
if (cached2) return cached2;
|
|
207
|
-
const url = process.env.SQUADBASE_POSTGRESQL_URL;
|
|
208
|
-
if (!url) throw new Error("SQUADBASE_POSTGRESQL_URL environment variable is not set");
|
|
209
|
-
const client = createPostgreSQLClient(url);
|
|
210
|
-
clientCache.set(cacheKey, client);
|
|
211
|
-
return client;
|
|
212
|
-
}
|
|
213
|
-
const cached = clientCache.get(connectorSlug);
|
|
214
|
-
if (cached) return cached;
|
|
215
|
-
const connections = loadConnections();
|
|
216
|
-
const entry = connections[connectorSlug];
|
|
217
|
-
if (!entry) {
|
|
218
|
-
throw new Error(`connector slug '${connectorSlug}' not found in .squadbase/connections.json`);
|
|
219
|
-
}
|
|
220
|
-
const resolvedType = connectorType ?? entry.connectorType;
|
|
221
|
-
if (!resolvedType) {
|
|
222
|
-
throw new Error(
|
|
223
|
-
`connector type could not be determined for slug '${connectorSlug}'. Specify connectorType in the data-source JSON or in .squadbase/connections.json.`
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
if (resolvedType === "snowflake") {
|
|
227
|
-
return createSnowflakeClient(entry, connectorSlug);
|
|
228
|
-
}
|
|
229
|
-
if (resolvedType === "bigquery") {
|
|
230
|
-
return createBigQueryClient(entry, connectorSlug);
|
|
231
|
-
}
|
|
232
|
-
if (resolvedType === "postgresql" || resolvedType === "squadbase-db") {
|
|
233
|
-
const urlEnvName = entry.envVars["connection-url"];
|
|
234
|
-
if (!urlEnvName) {
|
|
235
|
-
throw new Error(`'connection-url' is not defined in envVars for connector '${connectorSlug}'`);
|
|
236
|
-
}
|
|
237
|
-
const connectionUrl = process.env[urlEnvName];
|
|
238
|
-
if (!connectionUrl) {
|
|
239
|
-
throw new Error(
|
|
240
|
-
`environment variable '${urlEnvName}' (mapped from connector '${connectorSlug}') is not set`
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
const client = createPostgreSQLClient(connectionUrl);
|
|
244
|
-
clientCache.set(connectorSlug, client);
|
|
245
|
-
return client;
|
|
246
|
-
}
|
|
247
|
-
throw new Error(
|
|
248
|
-
`connector type '${resolvedType}' is not supported. Supported: "snowflake", "bigquery", "postgresql", "squadbase-db"`
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
function reloadEnvFile2(envPath) {
|
|
252
|
-
try {
|
|
253
|
-
const raw = readFileSync(envPath, "utf-8");
|
|
254
|
-
for (const line of raw.split("\n")) {
|
|
255
|
-
const trimmed = line.trim();
|
|
256
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
257
|
-
const eqIdx = trimmed.indexOf("=");
|
|
258
|
-
if (eqIdx === -1) continue;
|
|
259
|
-
const key = trimmed.slice(0, eqIdx).trim();
|
|
260
|
-
const value = trimmed.slice(eqIdx + 1).trim();
|
|
261
|
-
if (key) process.env[key] = value;
|
|
262
|
-
}
|
|
263
|
-
console.log("[connector-client] .env reloaded");
|
|
264
|
-
} catch {
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
function watchConnectionsFile2() {
|
|
268
|
-
const filePath = getConnectionsFilePath();
|
|
269
|
-
const envPath = path.join(process.cwd(), "..", "..", ".env");
|
|
270
|
-
try {
|
|
271
|
-
fsWatch(filePath, { persistent: false }, () => {
|
|
272
|
-
console.log("[connector-client] connections.json changed, clearing cache");
|
|
273
|
-
connectionsCache = null;
|
|
274
|
-
clientCache.clear();
|
|
275
|
-
setImmediate(() => reloadEnvFile2(envPath));
|
|
276
|
-
});
|
|
277
|
-
} catch {
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return { getClient: getClient2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// src/connector-client/index.ts
|
|
284
|
-
var { getClient, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
|
|
285
|
-
|
|
286
|
-
// src/cli/env-loader.ts
|
|
287
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
288
|
-
function loadEnvFile(envPath) {
|
|
289
|
-
reloadEnvFile(envPath);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// src/cli/runner.ts
|
|
293
|
-
import { pathToFileURL } from "url";
|
|
294
|
-
import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
|
|
295
|
-
import path3 from "path";
|
|
296
|
-
|
|
297
|
-
// src/registry.ts
|
|
298
|
-
import { readdir, readFile, mkdir } from "fs/promises";
|
|
299
|
-
import { watch as fsWatch2 } from "fs";
|
|
300
|
-
import path2 from "path";
|
|
301
|
-
function buildQuery(queryTemplate, parameterMeta, runtimeParams) {
|
|
302
|
-
const defaults = new Map(
|
|
303
|
-
parameterMeta.map((p) => [p.name, p.default ?? null])
|
|
304
|
-
);
|
|
305
|
-
const placeholderToIndex = /* @__PURE__ */ new Map();
|
|
306
|
-
const values = [];
|
|
307
|
-
const text = queryTemplate.replace(
|
|
308
|
-
/\{\{(\w+)\}\}/g,
|
|
309
|
-
(_match, name) => {
|
|
310
|
-
if (!placeholderToIndex.has(name)) {
|
|
311
|
-
const value = Object.prototype.hasOwnProperty.call(runtimeParams, name) ? runtimeParams[name] : defaults.get(name) ?? null;
|
|
312
|
-
values.push(value);
|
|
313
|
-
placeholderToIndex.set(name, values.length);
|
|
314
|
-
}
|
|
315
|
-
return `$${placeholderToIndex.get(name)}`;
|
|
316
|
-
}
|
|
317
|
-
);
|
|
318
|
-
return { text, values };
|
|
319
|
-
}
|
|
320
|
-
var defaultDataSourceDir = path2.join(process.cwd(), "data-source");
|
|
321
|
-
|
|
322
|
-
// src/cli/runner.ts
|
|
323
|
-
function createStubContext(params) {
|
|
324
|
-
const stub = {
|
|
325
|
-
req: {
|
|
326
|
-
json: () => Promise.resolve(params),
|
|
327
|
-
query: (name) => {
|
|
328
|
-
if (name === void 0) {
|
|
329
|
-
return Object.fromEntries(
|
|
330
|
-
Object.entries(params).map(([k, v2]) => [k, String(v2)])
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
const v = params[name];
|
|
334
|
-
return v !== void 0 ? String(v) : "";
|
|
335
|
-
},
|
|
336
|
-
param: (_name) => void 0,
|
|
337
|
-
header: (_name) => void 0,
|
|
338
|
-
raw: new Request("http://localhost/cli")
|
|
339
|
-
},
|
|
340
|
-
json: (data) => data,
|
|
341
|
-
text: (data) => data,
|
|
342
|
-
body: (data) => data,
|
|
343
|
-
env: {},
|
|
344
|
-
var: {},
|
|
345
|
-
get: (_key) => void 0,
|
|
346
|
-
set: () => {
|
|
347
|
-
}
|
|
348
|
-
};
|
|
349
|
-
return stub;
|
|
350
|
-
}
|
|
351
|
-
async function runSqlDataSource(slug, def, params, limit) {
|
|
352
|
-
const start = Date.now();
|
|
353
|
-
try {
|
|
354
|
-
const client = await getClient(def.connectorSlug, def.connectorType);
|
|
355
|
-
const isExternal = def.connectorType === "snowflake" || def.connectorType === "bigquery";
|
|
356
|
-
let queryText;
|
|
357
|
-
let queryValues;
|
|
358
|
-
if (isExternal) {
|
|
359
|
-
const defaults = new Map(
|
|
360
|
-
(def.parameters ?? []).map((p) => [p.name, p.default ?? null])
|
|
361
|
-
);
|
|
362
|
-
queryText = def.query.replace(/\{\{(\w+)\}\}/g, (_match, name) => {
|
|
363
|
-
const value = Object.prototype.hasOwnProperty.call(params, name) ? params[name] : defaults.get(name) ?? "";
|
|
364
|
-
if (typeof value === "string") return `'${value.replace(/'/g, "''")}'`;
|
|
365
|
-
if (value === null || value === void 0) return "NULL";
|
|
366
|
-
return String(value);
|
|
367
|
-
});
|
|
368
|
-
queryValues = [];
|
|
369
|
-
} else {
|
|
370
|
-
const built = buildQuery(def.query, def.parameters ?? [], params);
|
|
371
|
-
queryText = built.text;
|
|
372
|
-
queryValues = built.values;
|
|
373
|
-
}
|
|
374
|
-
const result = await client.query(queryText, queryValues);
|
|
375
|
-
const rows = result.rows.slice(0, limit);
|
|
376
|
-
return {
|
|
377
|
-
slug,
|
|
378
|
-
rows,
|
|
379
|
-
rowCount: result.rows.length,
|
|
380
|
-
durationMs: Date.now() - start,
|
|
381
|
-
query: queryText,
|
|
382
|
-
queryValues
|
|
383
|
-
};
|
|
384
|
-
} catch (error) {
|
|
385
|
-
return {
|
|
386
|
-
slug,
|
|
387
|
-
rows: [],
|
|
388
|
-
rowCount: 0,
|
|
389
|
-
durationMs: Date.now() - start,
|
|
390
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
async function runTypescriptDataSource(slug, handlerPath, params) {
|
|
395
|
-
const start = Date.now();
|
|
396
|
-
try {
|
|
397
|
-
const mod = await import(pathToFileURL(handlerPath).href);
|
|
398
|
-
const handler = mod.default;
|
|
399
|
-
if (typeof handler !== "function") {
|
|
400
|
-
throw new Error(`Handler must export a default function: ${handlerPath}`);
|
|
401
|
-
}
|
|
402
|
-
const ctx = createStubContext(params);
|
|
403
|
-
const raw = await handler(ctx);
|
|
404
|
-
let rows;
|
|
405
|
-
if (Array.isArray(raw)) {
|
|
406
|
-
rows = raw;
|
|
407
|
-
} else if (raw !== null && typeof raw === "object") {
|
|
408
|
-
rows = [raw];
|
|
409
|
-
} else {
|
|
410
|
-
rows = [{ result: raw }];
|
|
411
|
-
}
|
|
412
|
-
return {
|
|
413
|
-
slug,
|
|
414
|
-
rows,
|
|
415
|
-
rowCount: rows.length,
|
|
416
|
-
durationMs: Date.now() - start
|
|
417
|
-
};
|
|
418
|
-
} catch (error) {
|
|
419
|
-
return {
|
|
420
|
-
slug,
|
|
421
|
-
rows: [],
|
|
422
|
-
rowCount: 0,
|
|
423
|
-
durationMs: Date.now() - start,
|
|
424
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
async function runDataSource(slug, dirPath, params, limit) {
|
|
429
|
-
const jsonPath = path3.join(dirPath, `${slug}.json`);
|
|
430
|
-
let def;
|
|
431
|
-
try {
|
|
432
|
-
const raw = await readFile2(jsonPath, "utf-8");
|
|
433
|
-
def = JSON.parse(raw);
|
|
434
|
-
} catch {
|
|
435
|
-
return {
|
|
436
|
-
slug,
|
|
437
|
-
rows: [],
|
|
438
|
-
rowCount: 0,
|
|
439
|
-
durationMs: 0,
|
|
440
|
-
error: new Error(`Data source not found: ${jsonPath}`)
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
if (def.type === "typescript") {
|
|
444
|
-
const absolutePath = path3.resolve(dirPath, def.handlerPath);
|
|
445
|
-
return runTypescriptDataSource(slug, absolutePath, params);
|
|
446
|
-
}
|
|
447
|
-
return runSqlDataSource(slug, def, params, limit);
|
|
448
|
-
}
|
|
449
|
-
async function listSlugs(dirPath) {
|
|
450
|
-
try {
|
|
451
|
-
const files = await readdir2(dirPath);
|
|
452
|
-
return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
|
|
453
|
-
} catch {
|
|
454
|
-
return [];
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
async function runAll(dirPath, params, limit) {
|
|
458
|
-
const slugs = await listSlugs(dirPath);
|
|
459
|
-
return Promise.all(slugs.map((slug) => runDataSource(slug, dirPath, params, limit)));
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// src/cli/display.ts
|
|
463
|
-
var RESET = "\x1B[0m";
|
|
464
|
-
var BOLD = "\x1B[1m";
|
|
465
|
-
var RED = "\x1B[31m";
|
|
466
|
-
var GREEN = "\x1B[32m";
|
|
467
|
-
var YELLOW = "\x1B[33m";
|
|
468
|
-
var CYAN = "\x1B[36m";
|
|
469
|
-
var DIM = "\x1B[2m";
|
|
470
|
-
function truncate(value, maxLen) {
|
|
471
|
-
return value.length > maxLen ? value.slice(0, maxLen - 1) + "\u2026" : value;
|
|
472
|
-
}
|
|
473
|
-
function formatValue(value) {
|
|
474
|
-
if (value === null || value === void 0) return DIM + "null" + RESET;
|
|
475
|
-
if (typeof value === "object") return JSON.stringify(value);
|
|
476
|
-
return String(value);
|
|
477
|
-
}
|
|
478
|
-
function displayTable(rows, limit) {
|
|
479
|
-
if (rows.length === 0) {
|
|
480
|
-
console.log(DIM + " (no rows)" + RESET);
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
const display = rows.slice(0, limit);
|
|
484
|
-
const columns = Object.keys(display[0] ?? {});
|
|
485
|
-
const colWidths = columns.map((col) => {
|
|
486
|
-
const maxData = Math.max(...display.map((r) => formatValue(r[col]).replace(/\x1b\[[0-9;]*m/g, "").length));
|
|
487
|
-
return Math.min(40, Math.max(col.length, maxData));
|
|
488
|
-
});
|
|
489
|
-
const header = columns.map((c, i) => BOLD + CYAN + c.padEnd(colWidths[i] ?? 0) + RESET).join(" ");
|
|
490
|
-
const divider = colWidths.map((w) => "-".repeat(w)).join("--");
|
|
491
|
-
console.log(" " + header);
|
|
492
|
-
console.log(" " + DIM + divider + RESET);
|
|
493
|
-
for (const row of display) {
|
|
494
|
-
const line = columns.map((c, i) => {
|
|
495
|
-
const raw = formatValue(row[c]);
|
|
496
|
-
const plain = raw.replace(/\x1b\[[0-9;]*m/g, "");
|
|
497
|
-
const padded = truncate(plain, colWidths[i] ?? 40).padEnd(colWidths[i] ?? 0);
|
|
498
|
-
return raw.startsWith(DIM) ? DIM + padded + RESET : padded;
|
|
499
|
-
}).join(" ");
|
|
500
|
-
console.log(" " + line);
|
|
501
|
-
}
|
|
502
|
-
if (rows.length > limit) {
|
|
503
|
-
console.log(DIM + ` \u2026 and ${rows.length - limit} more rows (use --limit to show more)` + RESET);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
function displaySummary(result) {
|
|
507
|
-
const status = result.error ? RED + "\u2717 FAIL" + RESET : GREEN + "\u2713 OK" + RESET;
|
|
508
|
-
const duration = DIM + `(${result.durationMs}ms)` + RESET;
|
|
509
|
-
console.log(`
|
|
510
|
-
${BOLD}[${result.slug}]${RESET} ${status} ${duration}`);
|
|
511
|
-
if (result.error) {
|
|
512
|
-
console.log(RED + ` Error: ${result.error.message}` + RESET);
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
console.log(DIM + ` ${result.rowCount} row(s)` + RESET);
|
|
516
|
-
}
|
|
517
|
-
function displayDebug(result) {
|
|
518
|
-
if (result.query) {
|
|
519
|
-
console.log(YELLOW + " Query:" + RESET);
|
|
520
|
-
console.log(DIM + ` ${result.query.replace(/\n/g, "\n ")}` + RESET);
|
|
521
|
-
}
|
|
522
|
-
if (result.queryValues && result.queryValues.length > 0) {
|
|
523
|
-
console.log(YELLOW + " Params:" + RESET, result.queryValues);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
function displayJson(results) {
|
|
527
|
-
const output = results.map((r) => ({
|
|
528
|
-
slug: r.slug,
|
|
529
|
-
rowCount: r.rowCount,
|
|
530
|
-
durationMs: r.durationMs,
|
|
531
|
-
rows: r.rows,
|
|
532
|
-
error: r.error?.message
|
|
533
|
-
}));
|
|
534
|
-
console.log(JSON.stringify(output, null, 2));
|
|
535
|
-
}
|
|
536
|
-
function displayError(error) {
|
|
537
|
-
console.error(RED + "Error: " + error.message + RESET);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// src/cli/index.ts
|
|
541
|
-
var HELP = `
|
|
542
|
-
Usage: squadbase-ds-test [options]
|
|
543
|
-
|
|
544
|
-
Options:
|
|
545
|
-
--slug <slug> Run a specific data source
|
|
546
|
-
--all Run all data sources
|
|
547
|
-
--params k=v,... Comma-separated key=value parameters
|
|
548
|
-
--env <path> Path to .env file (default: ../../.env)
|
|
549
|
-
--dir <path> Data source directory (default: ./data-source)
|
|
550
|
-
--format table|json Output format (default: table)
|
|
551
|
-
--limit <n> Max rows to display (default: 50)
|
|
552
|
-
--debug Show SQL query and parameter values
|
|
553
|
-
--help Show this help
|
|
554
|
-
|
|
555
|
-
Examples:
|
|
556
|
-
npx tsx src/cli/index.ts --slug sales-summary
|
|
557
|
-
npx tsx src/cli/index.ts --slug sales-summary --params year=2024,limit=10
|
|
558
|
-
npx tsx src/cli/index.ts --all --format json
|
|
559
|
-
npx tsx src/cli/index.ts # interactive mode
|
|
560
|
-
`;
|
|
561
|
-
async function main() {
|
|
562
|
-
const { values } = parseArgs({
|
|
563
|
-
options: {
|
|
564
|
-
slug: { type: "string" },
|
|
565
|
-
all: { type: "boolean", default: false },
|
|
566
|
-
params: { type: "string" },
|
|
567
|
-
env: { type: "string" },
|
|
568
|
-
dir: { type: "string" },
|
|
569
|
-
format: { type: "string", default: "table" },
|
|
570
|
-
limit: { type: "string", default: "50" },
|
|
571
|
-
debug: { type: "boolean", default: false },
|
|
572
|
-
help: { type: "boolean", default: false }
|
|
573
|
-
},
|
|
574
|
-
allowPositionals: false
|
|
575
|
-
});
|
|
576
|
-
if (values.help) {
|
|
577
|
-
console.log(HELP);
|
|
578
|
-
process.exit(0);
|
|
579
|
-
}
|
|
580
|
-
const cwd = process.cwd();
|
|
581
|
-
const dirPath = values.dir ? path4.resolve(cwd, values.dir) : path4.join(cwd, "data-source");
|
|
582
|
-
const envPath = values.env ? path4.resolve(cwd, values.env) : path4.join(cwd, "../../.env");
|
|
583
|
-
const limit = parseInt(values.limit ?? "50", 10);
|
|
584
|
-
const format = values.format ?? "table";
|
|
585
|
-
loadEnvFile(envPath);
|
|
586
|
-
const params = {};
|
|
587
|
-
if (values.params) {
|
|
588
|
-
for (const pair of values.params.split(",")) {
|
|
589
|
-
const eqIdx = pair.indexOf("=");
|
|
590
|
-
if (eqIdx === -1) continue;
|
|
591
|
-
const key = pair.slice(0, eqIdx).trim();
|
|
592
|
-
const val = pair.slice(eqIdx + 1).trim();
|
|
593
|
-
params[key] = val;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
if (values.slug) {
|
|
597
|
-
const result = await runDataSource(values.slug, dirPath, params, limit);
|
|
598
|
-
if (format === "json") {
|
|
599
|
-
displayJson([result]);
|
|
600
|
-
} else {
|
|
601
|
-
displaySummary(result);
|
|
602
|
-
if (values.debug) displayDebug(result);
|
|
603
|
-
if (!result.error) displayTable(result.rows, limit);
|
|
604
|
-
}
|
|
605
|
-
if (result.error) process.exit(1);
|
|
606
|
-
} else if (values.all) {
|
|
607
|
-
const results = await runAll(dirPath, params, limit);
|
|
608
|
-
if (format === "json") {
|
|
609
|
-
displayJson(results);
|
|
610
|
-
} else {
|
|
611
|
-
for (const r of results) {
|
|
612
|
-
displaySummary(r);
|
|
613
|
-
if (values.debug) displayDebug(r);
|
|
614
|
-
if (!r.error) displayTable(r.rows, limit);
|
|
615
|
-
}
|
|
616
|
-
const failed = results.filter((r) => r.error).length;
|
|
617
|
-
console.log(`
|
|
618
|
-
Total: ${results.length}, Failed: ${failed}`);
|
|
619
|
-
}
|
|
620
|
-
const anyFailed = results.some((r) => r.error);
|
|
621
|
-
if (anyFailed) process.exit(1);
|
|
622
|
-
} else {
|
|
623
|
-
const slugs = await listSlugs(dirPath);
|
|
624
|
-
if (slugs.length === 0) {
|
|
625
|
-
displayError(new Error(`No data sources found in ${dirPath}`));
|
|
626
|
-
process.exit(1);
|
|
627
|
-
}
|
|
628
|
-
try {
|
|
629
|
-
const { selectDataSource: selectDataSource2, inputParameters: inputParameters2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
630
|
-
const slug = await selectDataSource2(slugs);
|
|
631
|
-
if (!slug) {
|
|
632
|
-
console.log("Cancelled.");
|
|
633
|
-
process.exit(0);
|
|
634
|
-
}
|
|
635
|
-
const jsonPath = path4.join(dirPath, `${slug}.json`);
|
|
636
|
-
let paramMeta = [];
|
|
637
|
-
try {
|
|
638
|
-
const raw = await readFile3(jsonPath, "utf-8");
|
|
639
|
-
const def = JSON.parse(raw);
|
|
640
|
-
paramMeta = def.parameters ?? [];
|
|
641
|
-
} catch {
|
|
642
|
-
}
|
|
643
|
-
const interactiveParams = await inputParameters2(paramMeta);
|
|
644
|
-
const merged = { ...interactiveParams, ...params };
|
|
645
|
-
const result = await runDataSource(slug, dirPath, merged, limit);
|
|
646
|
-
displaySummary(result);
|
|
647
|
-
if (values.debug) displayDebug(result);
|
|
648
|
-
if (!result.error) displayTable(result.rows, limit);
|
|
649
|
-
if (result.error) process.exit(1);
|
|
650
|
-
} catch (err) {
|
|
651
|
-
displayError(err instanceof Error ? err : new Error(String(err)));
|
|
652
|
-
process.exit(1);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
main().catch((err) => {
|
|
657
|
-
displayError(err instanceof Error ? err : new Error(String(err)));
|
|
658
|
-
process.exit(1);
|
|
659
|
-
});
|