@squadbase/vite-server 0.0.1-build-1
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/cli/index.d.ts +1 -0
- package/dist/cli/index.js +659 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +743 -0
- package/dist/main.d.ts +7 -0
- package/dist/main.js +765 -0
- package/dist/types/data-source.d.ts +83 -0
- package/dist/types/data-source.js +0 -0
- package/dist/vite-plugin.d.ts +12 -0
- package/dist/vite-plugin.js +267 -0
- package/package.json +54 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
interface ParameterMeta {
|
|
2
|
+
name: string;
|
|
3
|
+
type: "string" | "number" | "boolean";
|
|
4
|
+
description: string;
|
|
5
|
+
required?: boolean;
|
|
6
|
+
default?: string | number | boolean;
|
|
7
|
+
}
|
|
8
|
+
interface DataSourceCacheConfig {
|
|
9
|
+
/**
|
|
10
|
+
* キャッシュの有効期間(秒)。
|
|
11
|
+
* 0 または未指定の場合はキャッシュしない(後方互換性を保つデフォルト動作)。
|
|
12
|
+
*/
|
|
13
|
+
ttl: number;
|
|
14
|
+
/**
|
|
15
|
+
* true の場合、TTL 期限切れ後も古いデータを即座に返しつつ、
|
|
16
|
+
* バックグラウンドで新しいデータを非同期取得してキャッシュを更新する。
|
|
17
|
+
* デフォルト: false
|
|
18
|
+
*/
|
|
19
|
+
staleWhileRevalidate?: boolean;
|
|
20
|
+
}
|
|
21
|
+
interface DataSourceSchemaObject {
|
|
22
|
+
type?: "string" | "number" | "integer" | "boolean" | "object" | "array" | "null";
|
|
23
|
+
format?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
nullable?: boolean;
|
|
26
|
+
enum?: (string | number | boolean | null)[];
|
|
27
|
+
items?: DataSourceSchemaObject;
|
|
28
|
+
properties?: Record<string, DataSourceSchemaObject>;
|
|
29
|
+
required?: string[];
|
|
30
|
+
additionalProperties?: boolean | DataSourceSchemaObject;
|
|
31
|
+
minimum?: number;
|
|
32
|
+
maximum?: number;
|
|
33
|
+
minLength?: number;
|
|
34
|
+
maxLength?: number;
|
|
35
|
+
pattern?: string;
|
|
36
|
+
}
|
|
37
|
+
interface DataSourceMediaType {
|
|
38
|
+
schema?: DataSourceSchemaObject;
|
|
39
|
+
example?: unknown;
|
|
40
|
+
}
|
|
41
|
+
interface DataSourceResponse {
|
|
42
|
+
description?: string;
|
|
43
|
+
defaultContentType?: string;
|
|
44
|
+
content?: Record<string, DataSourceMediaType>;
|
|
45
|
+
}
|
|
46
|
+
interface DataSourceDefinition {
|
|
47
|
+
description: string;
|
|
48
|
+
parameters: ParameterMeta[];
|
|
49
|
+
response?: DataSourceResponse;
|
|
50
|
+
connectorSlug?: string;
|
|
51
|
+
cacheConfig?: DataSourceCacheConfig;
|
|
52
|
+
handler: (params: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
53
|
+
_isTypescript?: boolean;
|
|
54
|
+
_tsHandlerPath?: string;
|
|
55
|
+
}
|
|
56
|
+
interface DataSourceMeta {
|
|
57
|
+
slug: string;
|
|
58
|
+
description: string;
|
|
59
|
+
parameters: ParameterMeta[];
|
|
60
|
+
response?: DataSourceResponse;
|
|
61
|
+
connectorSlug?: string;
|
|
62
|
+
}
|
|
63
|
+
interface JsonDataSourceDefinition {
|
|
64
|
+
description: string;
|
|
65
|
+
type?: "sql";
|
|
66
|
+
parameters?: ParameterMeta[];
|
|
67
|
+
response?: DataSourceResponse;
|
|
68
|
+
query: string;
|
|
69
|
+
connectorType?: string;
|
|
70
|
+
connectorSlug?: string;
|
|
71
|
+
cache?: DataSourceCacheConfig;
|
|
72
|
+
}
|
|
73
|
+
interface JsonTypeScriptDataSourceDefinition {
|
|
74
|
+
description: string;
|
|
75
|
+
type: "typescript";
|
|
76
|
+
handlerPath: string;
|
|
77
|
+
parameters?: ParameterMeta[];
|
|
78
|
+
response?: DataSourceResponse;
|
|
79
|
+
cache?: DataSourceCacheConfig;
|
|
80
|
+
}
|
|
81
|
+
type AnyJsonDataSourceDefinition = JsonDataSourceDefinition | JsonTypeScriptDataSourceDefinition;
|
|
82
|
+
|
|
83
|
+
export type { AnyJsonDataSourceDefinition, DataSourceCacheConfig, DataSourceDefinition, DataSourceMediaType, DataSourceMeta, DataSourceResponse, DataSourceSchemaObject, JsonDataSourceDefinition, JsonTypeScriptDataSourceDefinition, ParameterMeta };
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface SquadbasePluginOptions {
|
|
4
|
+
buildEntry?: string;
|
|
5
|
+
devEntry?: string;
|
|
6
|
+
port?: number;
|
|
7
|
+
external?: string[];
|
|
8
|
+
exclude?: RegExp[];
|
|
9
|
+
}
|
|
10
|
+
declare function squadbasePlugin(options?: SquadbasePluginOptions): Plugin[];
|
|
11
|
+
|
|
12
|
+
export { squadbasePlugin };
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// src/vite-plugin.ts
|
|
2
|
+
import buildPlugin from "@hono/vite-build/node";
|
|
3
|
+
import devServer from "@hono/vite-dev-server";
|
|
4
|
+
import nodeAdapter from "@hono/vite-dev-server/node";
|
|
5
|
+
|
|
6
|
+
// src/registry.ts
|
|
7
|
+
import { readdir, readFile, mkdir } from "fs/promises";
|
|
8
|
+
import { watch as fsWatch2 } from "fs";
|
|
9
|
+
import path2 from "path";
|
|
10
|
+
|
|
11
|
+
// src/connector-client/registry.ts
|
|
12
|
+
import { readFileSync, watch as fsWatch } from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
|
|
15
|
+
// src/connector-client/postgresql.ts
|
|
16
|
+
import pg from "pg";
|
|
17
|
+
var { Pool } = pg;
|
|
18
|
+
function createPostgreSQLClient(connectionString) {
|
|
19
|
+
const pool = new Pool({ connectionString, ssl: { rejectUnauthorized: false } });
|
|
20
|
+
return {
|
|
21
|
+
async query(sql, params) {
|
|
22
|
+
const result = await pool.query(sql, params);
|
|
23
|
+
return { rows: result.rows };
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/connector-client/env.ts
|
|
29
|
+
function resolveEnvVar(entry, key, slug) {
|
|
30
|
+
const envVarName = entry.envVars[key];
|
|
31
|
+
if (!envVarName) {
|
|
32
|
+
throw new Error(`Connector "${slug}" is missing envVars mapping for key "${key}"`);
|
|
33
|
+
}
|
|
34
|
+
const value = process.env[envVarName];
|
|
35
|
+
if (!value) {
|
|
36
|
+
throw new Error(`Environment variable "${envVarName}" (for "${slug}.${key}") is not set`);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/connector-client/bigquery.ts
|
|
42
|
+
function createBigQueryClient(entry, slug) {
|
|
43
|
+
const projectId = resolveEnvVar(entry, "project-id", slug);
|
|
44
|
+
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64", slug);
|
|
45
|
+
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
46
|
+
let gcpCredentials;
|
|
47
|
+
try {
|
|
48
|
+
gcpCredentials = JSON.parse(serviceAccountJson);
|
|
49
|
+
} catch {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`BigQuery service account JSON (decoded from base64) is not valid JSON for slug "${slug}"`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
async query(sql) {
|
|
56
|
+
const { BigQuery } = await import("@google-cloud/bigquery");
|
|
57
|
+
const bq = new BigQuery({ projectId, credentials: gcpCredentials });
|
|
58
|
+
const [job] = await bq.createQueryJob({ query: sql });
|
|
59
|
+
const [allRows] = await job.getQueryResults({ timeoutMs: 3e4 });
|
|
60
|
+
return { rows: allRows };
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/connector-client/snowflake.ts
|
|
66
|
+
function createSnowflakeClient(entry, slug) {
|
|
67
|
+
const accountIdentifier = resolveEnvVar(entry, "account", slug);
|
|
68
|
+
const user = resolveEnvVar(entry, "user", slug);
|
|
69
|
+
const role = resolveEnvVar(entry, "role", slug);
|
|
70
|
+
const warehouse = resolveEnvVar(entry, "warehouse", slug);
|
|
71
|
+
const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64", slug);
|
|
72
|
+
const privateKey = Buffer.from(privateKeyBase64, "base64").toString("utf-8");
|
|
73
|
+
return {
|
|
74
|
+
async query(sql) {
|
|
75
|
+
const snowflake = (await import("snowflake-sdk")).default;
|
|
76
|
+
snowflake.configure({ logLevel: "ERROR" });
|
|
77
|
+
const connection = snowflake.createConnection({
|
|
78
|
+
account: accountIdentifier,
|
|
79
|
+
username: user,
|
|
80
|
+
role,
|
|
81
|
+
warehouse,
|
|
82
|
+
authenticator: "SNOWFLAKE_JWT",
|
|
83
|
+
privateKey
|
|
84
|
+
});
|
|
85
|
+
await new Promise((resolve, reject) => {
|
|
86
|
+
connection.connect((err) => {
|
|
87
|
+
if (err) reject(new Error(`Snowflake connect failed: ${err.message}`));
|
|
88
|
+
else resolve();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
const rows = await new Promise((resolve, reject) => {
|
|
92
|
+
connection.execute({
|
|
93
|
+
sqlText: sql,
|
|
94
|
+
complete: (err, _stmt, rows2) => {
|
|
95
|
+
if (err) reject(new Error(`Snowflake query failed: ${err.message}`));
|
|
96
|
+
else resolve(rows2 ?? []);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
connection.destroy((err) => {
|
|
101
|
+
if (err) console.warn(`[connector-client] Snowflake destroy error: ${err.message}`);
|
|
102
|
+
});
|
|
103
|
+
return { rows };
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/connector-client/registry.ts
|
|
109
|
+
function createConnectorRegistry() {
|
|
110
|
+
let connectionsCache = null;
|
|
111
|
+
const clientCache = /* @__PURE__ */ new Map();
|
|
112
|
+
function getConnectionsFilePath() {
|
|
113
|
+
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), "../../.squadbase/connections.json");
|
|
114
|
+
}
|
|
115
|
+
function loadConnections() {
|
|
116
|
+
if (connectionsCache !== null) return connectionsCache;
|
|
117
|
+
const filePath = getConnectionsFilePath();
|
|
118
|
+
try {
|
|
119
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
120
|
+
connectionsCache = JSON.parse(raw);
|
|
121
|
+
} catch {
|
|
122
|
+
connectionsCache = {};
|
|
123
|
+
}
|
|
124
|
+
return connectionsCache;
|
|
125
|
+
}
|
|
126
|
+
async function getClient2(connectorSlug, connectorType) {
|
|
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;
|
|
139
|
+
const connections = loadConnections();
|
|
140
|
+
const entry = connections[connectorSlug];
|
|
141
|
+
if (!entry) {
|
|
142
|
+
throw new Error(`connector slug '${connectorSlug}' not found in .squadbase/connections.json`);
|
|
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
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (resolvedType === "snowflake") {
|
|
151
|
+
return createSnowflakeClient(entry, connectorSlug);
|
|
152
|
+
}
|
|
153
|
+
if (resolvedType === "bigquery") {
|
|
154
|
+
return createBigQueryClient(entry, connectorSlug);
|
|
155
|
+
}
|
|
156
|
+
if (resolvedType === "postgresql" || resolvedType === "squadbase-db") {
|
|
157
|
+
const urlEnvName = entry.envVars["connection-url"];
|
|
158
|
+
if (!urlEnvName) {
|
|
159
|
+
throw new Error(`'connection-url' is not defined in envVars for connector '${connectorSlug}'`);
|
|
160
|
+
}
|
|
161
|
+
const connectionUrl = process.env[urlEnvName];
|
|
162
|
+
if (!connectionUrl) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`environment variable '${urlEnvName}' (mapped from connector '${connectorSlug}') is not set`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const client = createPostgreSQLClient(connectionUrl);
|
|
168
|
+
clientCache.set(connectorSlug, client);
|
|
169
|
+
return client;
|
|
170
|
+
}
|
|
171
|
+
throw new Error(
|
|
172
|
+
`connector type '${resolvedType}' is not supported. Supported: "snowflake", "bigquery", "postgresql", "squadbase-db"`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
function reloadEnvFile2(envPath) {
|
|
176
|
+
try {
|
|
177
|
+
const raw = readFileSync(envPath, "utf-8");
|
|
178
|
+
for (const line of raw.split("\n")) {
|
|
179
|
+
const trimmed = line.trim();
|
|
180
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
181
|
+
const eqIdx = trimmed.indexOf("=");
|
|
182
|
+
if (eqIdx === -1) continue;
|
|
183
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
184
|
+
const value = trimmed.slice(eqIdx + 1).trim();
|
|
185
|
+
if (key) process.env[key] = value;
|
|
186
|
+
}
|
|
187
|
+
console.log("[connector-client] .env reloaded");
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function watchConnectionsFile2() {
|
|
192
|
+
const filePath = getConnectionsFilePath();
|
|
193
|
+
const envPath = path.join(process.cwd(), "..", "..", ".env");
|
|
194
|
+
try {
|
|
195
|
+
fsWatch(filePath, { persistent: false }, () => {
|
|
196
|
+
console.log("[connector-client] connections.json changed, clearing cache");
|
|
197
|
+
connectionsCache = null;
|
|
198
|
+
clientCache.clear();
|
|
199
|
+
setImmediate(() => reloadEnvFile2(envPath));
|
|
200
|
+
});
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { getClient: getClient2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/connector-client/index.ts
|
|
208
|
+
var { getClient, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
|
|
209
|
+
|
|
210
|
+
// src/registry.ts
|
|
211
|
+
var viteServer = null;
|
|
212
|
+
function setViteServer(server) {
|
|
213
|
+
viteServer = server;
|
|
214
|
+
}
|
|
215
|
+
var defaultDataSourceDir = path2.join(process.cwd(), "data-source");
|
|
216
|
+
|
|
217
|
+
// src/vite-plugin.ts
|
|
218
|
+
var DEFAULT_EXCLUDE = [
|
|
219
|
+
/.*\.css$/,
|
|
220
|
+
/.*\.ts$/,
|
|
221
|
+
/.*\.tsx$/,
|
|
222
|
+
/^\/@.+$/,
|
|
223
|
+
/\?t\=\d+$/,
|
|
224
|
+
/^\/favicon\.ico$/,
|
|
225
|
+
/^\/static\/.+/,
|
|
226
|
+
/^\/node_modules\/.*/,
|
|
227
|
+
/^(?!\/api)/
|
|
228
|
+
];
|
|
229
|
+
function squadbasePlugin(options = {}) {
|
|
230
|
+
const {
|
|
231
|
+
buildEntry = "@squadbase/vite-server/main",
|
|
232
|
+
devEntry = "@squadbase/vite-server",
|
|
233
|
+
port = 3285,
|
|
234
|
+
external = ["pg", "@google-cloud/bigquery", "snowflake-sdk"],
|
|
235
|
+
exclude = DEFAULT_EXCLUDE
|
|
236
|
+
} = options;
|
|
237
|
+
const isServerBuild = (_, { command, mode }) => command === "build" && mode !== "client";
|
|
238
|
+
const rawBuildPlugin = buildPlugin({
|
|
239
|
+
entry: buildEntry,
|
|
240
|
+
outputDir: "./dist/server",
|
|
241
|
+
output: "index.js",
|
|
242
|
+
port,
|
|
243
|
+
external
|
|
244
|
+
});
|
|
245
|
+
const rawDevServerPlugin = devServer({
|
|
246
|
+
entry: devEntry,
|
|
247
|
+
adapter: nodeAdapter,
|
|
248
|
+
exclude
|
|
249
|
+
});
|
|
250
|
+
const buildPlugins = (Array.isArray(rawBuildPlugin) ? rawBuildPlugin : [rawBuildPlugin]).map(
|
|
251
|
+
(p) => ({ ...p, apply: isServerBuild })
|
|
252
|
+
);
|
|
253
|
+
const devPlugins = (Array.isArray(rawDevServerPlugin) ? rawDevServerPlugin : [rawDevServerPlugin]).map(
|
|
254
|
+
(p) => ({ ...p, apply: "serve" })
|
|
255
|
+
);
|
|
256
|
+
const viteServerInjectionPlugin = {
|
|
257
|
+
name: "squadbase-vite-server-injection",
|
|
258
|
+
apply: "serve",
|
|
259
|
+
configureServer(server) {
|
|
260
|
+
setViteServer(server);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
return [...buildPlugins, ...devPlugins, viteServerInjectionPlugin];
|
|
264
|
+
}
|
|
265
|
+
export {
|
|
266
|
+
squadbasePlugin
|
|
267
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@squadbase/vite-server",
|
|
3
|
+
"version": "0.0.1-build-1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"import": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts"
|
|
9
|
+
},
|
|
10
|
+
"./main": {
|
|
11
|
+
"import": "./dist/main.js",
|
|
12
|
+
"types": "./dist/main.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./types": {
|
|
15
|
+
"import": "./dist/types/data-source.js",
|
|
16
|
+
"types": "./dist/types/data-source.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./plugin": {
|
|
19
|
+
"import": "./dist/vite-plugin.js",
|
|
20
|
+
"types": "./dist/vite-plugin.d.ts"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": ["dist"],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"bin": {
|
|
28
|
+
"squadbase-ds-test": "./dist/cli/index.js"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"cli": "tsx src/cli/index.ts",
|
|
32
|
+
"build": "tsup && pnpm run build:cli",
|
|
33
|
+
"build:cli": "tsup src/cli/index.ts --out-dir dist/cli --format esm --platform node --no-splitting --external pg --external snowflake-sdk --external @google-cloud/bigquery --external hono --external @clack/prompts"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@google-cloud/bigquery": "^7.9.4",
|
|
37
|
+
"@hono/node-server": "^1.19.9",
|
|
38
|
+
"@hono/vite-build": "^1.10.0",
|
|
39
|
+
"@hono/vite-dev-server": "^0.18.0",
|
|
40
|
+
"hono": "^4.11.9",
|
|
41
|
+
"pg": "^8.18.0",
|
|
42
|
+
"snowflake-sdk": "^1.15.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@clack/prompts": "^0.9.1",
|
|
46
|
+
"@types/node": "^22.15.0",
|
|
47
|
+
"@types/pg": "^8.16.0",
|
|
48
|
+
"@types/snowflake-sdk": "^1.6.24",
|
|
49
|
+
"tsup": "^8.4.0",
|
|
50
|
+
"tsx": "^4.19.3",
|
|
51
|
+
"typescript": "~5.7.0",
|
|
52
|
+
"vite": "^6.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|