aboutsummaryrefslogtreecommitdiff
path: root/packages/db/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/db/src')
-rw-r--r--packages/db/src/core/cli/commands/execute/index.ts7
-rw-r--r--packages/db/src/core/cli/commands/link/index.ts295
-rw-r--r--packages/db/src/core/cli/commands/login/index.ts96
-rw-r--r--packages/db/src/core/cli/commands/logout/index.ts7
-rw-r--r--packages/db/src/core/cli/commands/push/index.ts40
-rw-r--r--packages/db/src/core/cli/commands/shell/index.ts4
-rw-r--r--packages/db/src/core/cli/commands/verify/index.ts4
-rw-r--r--packages/db/src/core/cli/index.ts20
-rw-r--r--packages/db/src/core/cli/migration-queries.ts38
-rw-r--r--packages/db/src/core/integration/index.ts12
-rw-r--r--packages/db/src/core/integration/vite-plugin-db.ts19
-rw-r--r--packages/db/src/core/load-file.ts2
-rw-r--r--packages/db/src/core/schemas.ts2
-rw-r--r--packages/db/src/core/utils.ts37
-rw-r--r--packages/db/src/runtime/db-client.ts183
15 files changed, 38 insertions, 728 deletions
diff --git a/packages/db/src/core/cli/commands/execute/index.ts b/packages/db/src/core/cli/commands/execute/index.ts
index 053736291..bf3dfd523 100644
--- a/packages/db/src/core/cli/commands/execute/index.ts
+++ b/packages/db/src/core/cli/commands/execute/index.ts
@@ -11,7 +11,7 @@ import {
} from '../../../errors.js';
import {
getLocalVirtualModContents,
- getStudioVirtualModContents,
+ getRemoteVirtualModContents,
} from '../../../integration/vite-plugin-db.js';
import { bundleFile, importBundledFile } from '../../../load-file.js';
import type { DBConfig } from '../../../types.js';
@@ -40,10 +40,9 @@ export async function cmd({
let virtualModContents: string;
if (flags.remote) {
- const appToken = await getManagedRemoteToken(flags.token);
- virtualModContents = getStudioVirtualModContents({
+ virtualModContents = getRemoteVirtualModContents({
tables: dbConfig.tables ?? {},
- appToken: appToken.token,
+ appToken: getManagedRemoteToken(flags.token),
isBuild: false,
output: 'server',
});
diff --git a/packages/db/src/core/cli/commands/link/index.ts b/packages/db/src/core/cli/commands/link/index.ts
deleted file mode 100644
index 4a105df9d..000000000
--- a/packages/db/src/core/cli/commands/link/index.ts
+++ /dev/null
@@ -1,295 +0,0 @@
-import { mkdir, writeFile } from 'node:fs/promises';
-import { homedir } from 'node:os';
-import { basename } from 'node:path';
-import {
- MISSING_SESSION_ID_ERROR,
- PROJECT_ID_FILE,
- getAstroStudioUrl,
- getSessionIdFromFile,
-} from '@astrojs/studio';
-import { slug } from 'github-slugger';
-import { bgRed, cyan } from 'kleur/colors';
-import prompts from 'prompts';
-import yoctoSpinner from 'yocto-spinner';
-import { safeFetch } from '../../../../runtime/utils.js';
-import type { Result } from '../../../utils.js';
-
-export async function cmd() {
- const sessionToken = await getSessionIdFromFile();
- if (!sessionToken) {
- console.error(MISSING_SESSION_ID_ERROR);
- process.exit(1);
- }
- await promptBegin();
- const isLinkExisting = await promptLinkExisting();
- if (isLinkExisting) {
- const workspaceId = await promptWorkspace(sessionToken);
- const existingProjectData = await promptExistingProjectName({ workspaceId });
- return await linkProject(existingProjectData.id);
- }
-
- const isLinkNew = await promptLinkNew();
- if (isLinkNew) {
- const workspaceId = await promptWorkspace(sessionToken);
- const newProjectName = await promptNewProjectName();
- const newProjectRegion = await promptNewProjectRegion();
- const spinner = yoctoSpinner({ text: 'Creating new project...' }).start();
- const newProjectData = await createNewProject({
- workspaceId,
- name: newProjectName,
- region: newProjectRegion,
- });
- // TODO(fks): Actually listen for project creation before continuing
- // This is just a dumb spinner that roughly matches database creation time.
- await new Promise((r) => setTimeout(r, 4000));
- spinner.success('Project created!');
- return await linkProject(newProjectData.id);
- }
-}
-
-async function linkProject(id: string) {
- await mkdir(new URL('.', PROJECT_ID_FILE), { recursive: true });
- await writeFile(PROJECT_ID_FILE, `${id}`);
- console.info('Project linked.');
-}
-
-async function getWorkspaces(sessionToken: string) {
- const linkUrl = new URL(getAstroStudioUrl() + '/api/cli/workspaces.list');
- const response = await safeFetch(
- linkUrl,
- {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${sessionToken}`,
- 'Content-Type': 'application/json',
- },
- },
- (res) => {
- // Unauthorized
- if (res.status === 401) {
- throw new Error(
- `${bgRed('Unauthorized')}\n\n Are you logged in?\n Run ${cyan(
- 'astro login',
- )} to authenticate and then try linking again.\n\n`,
- );
- }
- throw new Error(`Failed to fetch user workspace: ${res.status} ${res.statusText}`);
- },
- );
-
- const { data, success } = (await response.json()) as Result<{ id: string; name: string }[]>;
- if (!success) {
- throw new Error(`Failed to fetch user's workspace.`);
- }
- return data;
-}
-
-/**
- * Get the workspace ID to link to.
- * Prompts the user to choose if they have more than one workspace in Astro Studio.
- * @returns A `Promise` for the workspace ID to use.
- */
-async function promptWorkspace(sessionToken: string) {
- const workspaces = await getWorkspaces(sessionToken);
- if (workspaces.length === 0) {
- console.error('No workspaces found.');
- process.exit(1);
- }
-
- if (workspaces.length === 1) {
- return workspaces[0].id;
- }
-
- const { workspaceId } = await prompts({
- type: 'autocomplete',
- name: 'workspaceId',
- message: 'Select your workspace:',
- limit: 5,
- choices: workspaces.map((w) => ({ title: w.name, value: w.id })),
- });
- if (typeof workspaceId !== 'string') {
- console.log('Canceled.');
- process.exit(0);
- }
- return workspaceId;
-}
-
-async function createNewProject({
- workspaceId,
- name,
- region,
-}: {
- workspaceId: string;
- name: string;
- region: string;
-}) {
- const linkUrl = new URL(getAstroStudioUrl() + '/api/cli/projects.create');
- const response = await safeFetch(
- linkUrl,
- {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${await getSessionIdFromFile()}`,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ workspaceId, name, region }),
- },
- (res) => {
- // Unauthorized
- if (res.status === 401) {
- console.error(
- `${bgRed('Unauthorized')}\n\n Are you logged in?\n Run ${cyan(
- 'astro login',
- )} to authenticate and then try linking again.\n\n`,
- );
- process.exit(1);
- }
- console.error(`Failed to create project: ${res.status} ${res.statusText}`);
- process.exit(1);
- },
- );
-
- const { data, success } = (await response.json()) as Result<{ id: string; idName: string }>;
- if (!success) {
- console.error(`Failed to create project.`);
- process.exit(1);
- }
- return { id: data.id, idName: data.idName };
-}
-
-async function promptExistingProjectName({ workspaceId }: { workspaceId: string }) {
- const linkUrl = new URL(getAstroStudioUrl() + '/api/cli/projects.list');
- const response = await safeFetch(
- linkUrl,
- {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${await getSessionIdFromFile()}`,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ workspaceId }),
- },
- (res) => {
- if (res.status === 401) {
- console.error(
- `${bgRed('Unauthorized')}\n\n Are you logged in?\n Run ${cyan(
- 'astro login',
- )} to authenticate and then try linking again.\n\n`,
- );
- process.exit(1);
- }
- console.error(`Failed to fetch projects: ${res.status} ${res.statusText}`);
- process.exit(1);
- },
- );
-
- const { data, success } = (await response.json()) as Result<
- { id: string; name: string; idName: string }[]
- >;
- if (!success) {
- console.error(`Failed to fetch projects.`);
- process.exit(1);
- }
- const { projectId } = await prompts({
- type: 'autocomplete',
- name: 'projectId',
- message: 'What is your project name?',
- limit: 5,
- choices: data.map((p) => ({ title: p.name, value: p.id })),
- });
- if (typeof projectId !== 'string') {
- console.log('Canceled.');
- process.exit(0);
- }
- const selectedProjectData = data.find((p: any) => p.id === projectId)!;
- return selectedProjectData;
-}
-
-async function promptBegin(): Promise<void> {
- // Get the current working directory relative to the user's home directory
- const prettyCwd = process.cwd().replace(homedir(), '~');
-
- // prompt
- const { begin } = await prompts({
- type: 'confirm',
- name: 'begin',
- message: `Link "${prettyCwd}" with Astro Studio?`,
- initial: true,
- });
- if (!begin) {
- console.log('Canceled.');
- process.exit(0);
- }
-}
-
-/**
- * Ask the user if they want to link to an existing Astro Studio project.
- * @returns A `Promise` for the user’s answer: `true` if they answer yes, otherwise `false`.
- */
-async function promptLinkExisting(): Promise<boolean> {
- // prompt
- const { linkExisting } = await prompts({
- type: 'confirm',
- name: 'linkExisting',
- message: `Link with an existing project in Astro Studio?`,
- initial: true,
- });
- return !!linkExisting;
-}
-
-/**
- * Ask the user if they want to link to a new Astro Studio Project.
- * **Exits the process if they answer no.**
- * @returns A `Promise` for the user’s answer: `true` if they answer yes.
- */
-async function promptLinkNew(): Promise<boolean> {
- // prompt
- const { linkNew } = await prompts({
- type: 'confirm',
- name: 'linkNew',
- message: `Create a new project in Astro Studio?`,
- initial: true,
- });
- if (!linkNew) {
- console.log('Canceled.');
- process.exit(0);
- }
- return true;
-}
-
-async function promptNewProjectName(): Promise<string> {
- const { newProjectName } = await prompts({
- type: 'text',
- name: 'newProjectName',
- message: `What is your new project's name?`,
- initial: basename(process.cwd()),
- format: (val) => slug(val),
- });
- if (!newProjectName) {
- console.log('Canceled.');
- process.exit(0);
- }
- return newProjectName;
-}
-
-async function promptNewProjectRegion(): Promise<string> {
- const { newProjectRegion } = await prompts({
- type: 'select',
- name: 'newProjectRegion',
- message: `Where should your new database live?`,
- choices: [
- { title: 'North America (East)', value: 'NorthAmericaEast' },
- { title: 'North America (West)', value: 'NorthAmericaWest' },
- { title: 'Europe (Amsterdam)', value: 'EuropeCentral' },
- { title: 'South America (Brazil)', value: 'SouthAmericaEast' },
- { title: 'Asia (India)', value: 'AsiaSouth' },
- { title: 'Asia (Japan)', value: 'AsiaNorthEast' },
- ],
- initial: 0,
- });
- if (!newProjectRegion) {
- console.log('Canceled.');
- process.exit(0);
- }
- return newProjectRegion;
-}
diff --git a/packages/db/src/core/cli/commands/login/index.ts b/packages/db/src/core/cli/commands/login/index.ts
deleted file mode 100644
index 0b0979384..000000000
--- a/packages/db/src/core/cli/commands/login/index.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { mkdir, writeFile } from 'node:fs/promises';
-import { createServer as _createServer } from 'node:http';
-import { SESSION_LOGIN_FILE, getAstroStudioUrl } from '@astrojs/studio';
-import type { AstroConfig } from 'astro';
-import { listen } from 'async-listen';
-import { cyan } from 'kleur/colors';
-import open from 'open';
-import prompt from 'prompts';
-import type { Arguments } from 'yargs-parser';
-import yoctoSpinner from 'yocto-spinner';
-import type { DBConfig } from '../../../types.js';
-
-const isWebContainer =
- // Stackblitz heuristic
- process.versions?.webcontainer ??
- // GitHub Codespaces heuristic
- process.env.CODESPACE_NAME;
-
-export async function cmd({
- flags,
-}: {
- astroConfig: AstroConfig;
- dbConfig: DBConfig;
- flags: Arguments;
-}) {
- let session = flags.session;
-
- if (!session && isWebContainer) {
- console.log(`Please visit the following URL in your web browser:`);
- console.log(cyan(`${getAstroStudioUrl()}/auth/cli/login`));
- console.log(`After login in complete, enter the verification code displayed:`);
- const response = await prompt({
- type: 'text',
- name: 'session',
- message: 'Verification code:',
- });
- if (!response.session) {
- console.error('Cancelling login.');
- process.exit(0);
- }
- session = response.session;
- console.log('Successfully logged in');
- } else if (!session) {
- const { url, promise } = await createServer();
- const loginUrl = new URL('/auth/cli/login', getAstroStudioUrl());
- loginUrl.searchParams.set('returnTo', url);
- console.log(`Opening the following URL in your browser...`);
- console.log(cyan(loginUrl.href));
- console.log(`If something goes wrong, copy-and-paste the URL into your browser.`);
- open(loginUrl.href);
- const spinner = yoctoSpinner({ text: 'Waiting for confirmation...' });
- session = await promise;
- spinner.success('Successfully logged in');
- }
-
- await mkdir(new URL('.', SESSION_LOGIN_FILE), { recursive: true });
- await writeFile(SESSION_LOGIN_FILE, `${session}`);
-}
-
-// NOTE(fks): How the Astro CLI login process works:
-// 1. The Astro CLI creates a temporary server to listen for the session token
-// 2. The user is directed to studio.astro.build/ to login
-// 3. The user is redirected back to the temporary server with their session token
-// 4. The temporary server receives and saves the session token, logging the user in
-// 5. The user is redirected one last time to a success/failure page
-async function createServer(): Promise<{ url: string; promise: Promise<string> }> {
- let resolve: (value: string | PromiseLike<string>) => void, reject: (reason?: Error) => void;
-
- const server = _createServer((req, res) => {
- // Handle the request
- const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
- const sessionParam = url.searchParams.get('session');
- // Handle the response & resolve the promise
- res.statusCode = 302;
- if (!sessionParam) {
- res.setHeader('location', getAstroStudioUrl() + '/auth/cli/error');
- reject(new Error('Failed to log in'));
- } else {
- res.setHeader('location', getAstroStudioUrl() + '/auth/cli/success');
- resolve(sessionParam);
- }
- res.end();
- });
-
- const { port } = await listen(server, 0, '127.0.0.1');
- const serverUrl = `http://localhost:${port}`;
- const sessionPromise = new Promise<string>((_resolve, _reject) => {
- resolve = _resolve;
- reject = _reject;
- }).finally(() => {
- server.closeAllConnections();
- server.close();
- });
-
- return { url: serverUrl, promise: sessionPromise };
-}
diff --git a/packages/db/src/core/cli/commands/logout/index.ts b/packages/db/src/core/cli/commands/logout/index.ts
deleted file mode 100644
index 8b7878659..000000000
--- a/packages/db/src/core/cli/commands/logout/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { unlink } from 'node:fs/promises';
-import { SESSION_LOGIN_FILE } from '@astrojs/studio';
-
-export async function cmd() {
- await unlink(SESSION_LOGIN_FILE);
- console.log('Successfully logged out of Astro Studio.');
-}
diff --git a/packages/db/src/core/cli/commands/push/index.ts b/packages/db/src/core/cli/commands/push/index.ts
index 590d4f06e..46a457884 100644
--- a/packages/db/src/core/cli/commands/push/index.ts
+++ b/packages/db/src/core/cli/commands/push/index.ts
@@ -3,12 +3,10 @@ import { sql } from 'drizzle-orm';
import prompts from 'prompts';
import type { Arguments } from 'yargs-parser';
import { createRemoteDatabaseClient } from '../../../../runtime/index.js';
-import { safeFetch } from '../../../../runtime/utils.js';
import { MIGRATION_VERSION } from '../../../consts.js';
import type { DBConfig, DBSnapshot } from '../../../types.js';
import {
type RemoteDatabaseInfo,
- type Result,
getManagedRemoteToken,
getRemoteDatabaseInfo,
} from '../../../utils.js';
@@ -31,10 +29,10 @@ export async function cmd({
const isDryRun = flags.dryRun;
const isForceReset = flags.forceReset;
const dbInfo = getRemoteDatabaseInfo();
- const appToken = await getManagedRemoteToken(flags.token, dbInfo);
+ const appToken = getManagedRemoteToken(flags.token);
const productionSnapshot = await getProductionCurrentSnapshot({
dbInfo,
- appToken: appToken.token,
+ appToken: appToken,
});
const currentSnapshot = createCurrentSnapshot(dbConfig);
const isFromScratch = !productionSnapshot;
@@ -77,13 +75,11 @@ export async function cmd({
await pushSchema({
statements: migrationQueries,
dbInfo,
- appToken: appToken.token,
+ appToken: appToken,
isDryRun,
currentSnapshot: currentSnapshot,
});
}
- // cleanup and exit
- await appToken.destroy();
console.info('Push complete!');
}
@@ -110,9 +106,7 @@ async function pushSchema({
return new Response(null, { status: 200 });
}
- return dbInfo.type === 'studio'
- ? pushToStudio(requestBody, appToken, dbInfo.url)
- : pushToDb(requestBody, appToken, dbInfo.url);
+ return pushToDb(requestBody, appToken, dbInfo.url);
}
type RequestBody = {
@@ -145,29 +139,3 @@ async function pushToDb(requestBody: RequestBody, appToken: string, remoteUrl: s
)`);
});
}
-
-async function pushToStudio(requestBody: RequestBody, appToken: string, remoteUrl: string) {
- const url = new URL('/db/push', remoteUrl);
- const response = await safeFetch(
- url,
- {
- method: 'POST',
- headers: new Headers({
- Authorization: `Bearer ${appToken}`,
- }),
- body: JSON.stringify(requestBody),
- },
- async (res) => {
- console.error(`${url.toString()} failed: ${res.status} ${res.statusText}`);
- console.error(await res.text());
- throw new Error(`/db/push fetch failed: ${res.status} ${res.statusText}`);
- },
- );
-
- const result = (await response.json()) as Result<never>;
- if (!result.success) {
- console.error(`${url.toString()} unsuccessful`);
- console.error(await response.text());
- throw new Error(`/db/push fetch unsuccessful`);
- }
-}
diff --git a/packages/db/src/core/cli/commands/shell/index.ts b/packages/db/src/core/cli/commands/shell/index.ts
index dcc54fc70..278830b4e 100644
--- a/packages/db/src/core/cli/commands/shell/index.ts
+++ b/packages/db/src/core/cli/commands/shell/index.ts
@@ -26,14 +26,12 @@ export async function cmd({
}
const dbInfo = getRemoteDatabaseInfo();
if (flags.remote) {
- const appToken = await getManagedRemoteToken(flags.token, dbInfo);
const db = createRemoteDatabaseClient({
dbType: dbInfo.type,
remoteUrl: dbInfo.url,
- appToken: appToken.token,
+ appToken: getManagedRemoteToken(flags.token),
});
const result = await db.run(sql.raw(query));
- await appToken.destroy();
console.log(result);
} else {
const { ASTRO_DATABASE_FILE } = getAstroEnv();
diff --git a/packages/db/src/core/cli/commands/verify/index.ts b/packages/db/src/core/cli/commands/verify/index.ts
index 35f489a80..6499c272e 100644
--- a/packages/db/src/core/cli/commands/verify/index.ts
+++ b/packages/db/src/core/cli/commands/verify/index.ts
@@ -20,10 +20,9 @@ export async function cmd({
}) {
const isJson = flags.json;
const dbInfo = getRemoteDatabaseInfo();
- const appToken = await getManagedRemoteToken(flags.token, dbInfo);
const productionSnapshot = await getProductionCurrentSnapshot({
dbInfo,
- appToken: appToken.token,
+ appToken: getManagedRemoteToken(flags.token),
});
const currentSnapshot = createCurrentSnapshot(dbConfig);
const { queries: migrationQueries, confirmations } = await getMigrationQueries({
@@ -53,6 +52,5 @@ export async function cmd({
console.log(result.message);
}
- await appToken.destroy();
process.exit(result.exitCode);
}
diff --git a/packages/db/src/core/cli/index.ts b/packages/db/src/core/cli/index.ts
index 531b016a6..6c54c2ae6 100644
--- a/packages/db/src/core/cli/index.ts
+++ b/packages/db/src/core/cli/index.ts
@@ -41,18 +41,6 @@ export async function cli({
const { cmd } = await import('./commands/execute/index.js');
return await cmd({ astroConfig, dbConfig, flags });
}
- case 'login': {
- const { cmd } = await import('./commands/login/index.js');
- return await cmd({ astroConfig, dbConfig, flags });
- }
- case 'logout': {
- const { cmd } = await import('./commands/logout/index.js');
- return await cmd();
- }
- case 'link': {
- const { cmd } = await import('./commands/link/index.js');
- return await cmd();
- }
default: {
if (command != null) {
console.error(`Unknown command: ${command}`);
@@ -63,15 +51,15 @@ export async function cli({
headline: ' ',
tables: {
Commands: [
- ['push', 'Push table schema updates to Astro Studio.'],
- ['verify', 'Test schema updates /w Astro Studio (good for CI).'],
+ ['push', 'Push table schema updates to libSQL.'],
+ ['verify', 'Test schema updates /w libSQL (good for CI).'],
[
'astro db execute <file-path>',
- 'Execute a ts/js file using astro:db. Use --remote to connect to Studio.',
+ 'Execute a ts/js file using astro:db. Use --remote to connect to libSQL.',
],
[
'astro db shell --query <sql-string>',
- 'Execute a SQL string. Use --remote to connect to Studio.',
+ 'Execute a SQL string. Use --remote to connect to libSQL.',
],
],
},
diff --git a/packages/db/src/core/cli/migration-queries.ts b/packages/db/src/core/cli/migration-queries.ts
index db3972d09..0f369e684 100644
--- a/packages/db/src/core/cli/migration-queries.ts
+++ b/packages/db/src/core/cli/migration-queries.ts
@@ -7,7 +7,7 @@ import { customAlphabet } from 'nanoid';
import { hasPrimaryKey } from '../../runtime/index.js';
import { createRemoteDatabaseClient } from '../../runtime/index.js';
import { isSerializedSQL } from '../../runtime/types.js';
-import { isDbError, safeFetch } from '../../runtime/utils.js';
+import { isDbError } from '../../runtime/utils.js';
import { MIGRATION_VERSION } from '../consts.js';
import { RENAME_COLUMN_ERROR, RENAME_TABLE_ERROR } from '../errors.js';
import {
@@ -35,7 +35,7 @@ import type {
ResolvedIndexes,
TextColumn,
} from '../types.js';
-import type { RemoteDatabaseInfo, Result } from '../utils.js';
+import type { RemoteDatabaseInfo } from '../utils.js';
const sqlite = new SQLiteAsyncDialect();
const genTempTableName = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10);
@@ -428,9 +428,7 @@ export function getProductionCurrentSnapshot(options: {
dbInfo: RemoteDatabaseInfo;
appToken: string;
}): Promise<DBSnapshot | undefined> {
- return options.dbInfo.type === 'studio'
- ? getStudioCurrentSnapshot(options.appToken, options.dbInfo.url)
- : getDbCurrentSnapshot(options.appToken, options.dbInfo.url);
+ return getDbCurrentSnapshot(options.appToken, options.dbInfo.url);
}
async function getDbCurrentSnapshot(
@@ -473,36 +471,6 @@ async function getDbCurrentSnapshot(
}
}
-async function getStudioCurrentSnapshot(
- appToken: string,
- remoteUrl: string,
-): Promise<DBSnapshot | undefined> {
- const url = new URL('/db/schema', remoteUrl);
-
- const response = await safeFetch(
- url,
- {
- method: 'POST',
- headers: new Headers({
- Authorization: `Bearer ${appToken}`,
- }),
- },
- async (res) => {
- console.error(`${url.toString()} failed: ${res.status} ${res.statusText}`);
- console.error(await res.text());
- throw new Error(`/db/schema fetch failed: ${res.status} ${res.statusText}`);
- },
- );
-
- const result = (await response.json()) as Result<DBSnapshot>;
- if (!result.success) {
- console.error(`${url.toString()} unsuccessful`);
- console.error(await response.text());
- throw new Error(`/db/schema fetch unsuccessful`);
- }
- return result.data;
-}
-
function getDropTableQueriesForSnapshot(snapshot: DBSnapshot) {
const queries = [];
for (const tableName of Object.keys(snapshot.schema)) {
diff --git a/packages/db/src/core/integration/index.ts b/packages/db/src/core/integration/index.ts
index c6f58d2fd..bc038c7d2 100644
--- a/packages/db/src/core/integration/index.ts
+++ b/packages/db/src/core/integration/index.ts
@@ -2,7 +2,6 @@ import { existsSync } from 'node:fs';
import { mkdir, writeFile } from 'node:fs/promises';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
-import type { ManagedAppToken } from '@astrojs/studio';
import type { AstroIntegration } from 'astro';
import { blue, yellow } from 'kleur/colors';
import {
@@ -33,7 +32,6 @@ function astroDBIntegration(): AstroIntegration {
let connectToRemote = false;
let configFileDependencies: string[] = [];
let root: URL;
- let appToken: ManagedAppToken | undefined;
// Used during production builds to load seed files.
let tempViteServer: ViteDevServer | undefined;
@@ -72,10 +70,9 @@ function astroDBIntegration(): AstroIntegration {
connectToRemote = process.env.ASTRO_INTERNAL_TEST_REMOTE || args['remote'];
if (connectToRemote) {
- appToken = await getManagedRemoteToken();
dbPlugin = vitePluginDb({
- connectToStudio: connectToRemote,
- appToken: appToken.token,
+ connectToRemote: connectToRemote,
+ appToken: getManagedRemoteToken(),
tables,
root: config.root,
srcDir: config.srcDir,
@@ -84,7 +81,7 @@ function astroDBIntegration(): AstroIntegration {
});
} else {
dbPlugin = vitePluginDb({
- connectToStudio: false,
+ connectToRemote: false,
tables,
seedFiles,
root: config.root,
@@ -161,7 +158,7 @@ function astroDBIntegration(): AstroIntegration {
if (!connectToRemote && !databaseFileEnvDefined() && finalBuildOutput === 'server') {
const message = `Attempting to build without the --remote flag or the ASTRO_DATABASE_FILE environment variable defined. You probably want to pass --remote to astro build.`;
const hint =
- 'Learn more connecting to Studio: https://docs.astro.build/en/guides/astro-db/#connect-to-astro-studio';
+ 'Learn more connecting to libSQL: https://docs.astro.build/en/guides/astro-db/#connect-a-libsql-database-for-production';
throw new AstroDbError(message, hint);
}
@@ -174,7 +171,6 @@ function astroDBIntegration(): AstroIntegration {
};
},
'astro:build:done': async ({}) => {
- await appToken?.destroy();
await tempViteServer?.close();
},
},
diff --git a/packages/db/src/core/integration/vite-plugin-db.ts b/packages/db/src/core/integration/vite-plugin-db.ts
index 410d49157..7a7173bfc 100644
--- a/packages/db/src/core/integration/vite-plugin-db.ts
+++ b/packages/db/src/core/integration/vite-plugin-db.ts
@@ -34,7 +34,7 @@ export type SeedHandler = {
type VitePluginDBParams =
| {
- connectToStudio: false;
+ connectToRemote: false;
tables: LateTables;
seedFiles: LateSeedFiles;
srcDir: URL;
@@ -44,7 +44,7 @@ type VitePluginDBParams =
seedHandler: SeedHandler;
}
| {
- connectToStudio: true;
+ connectToRemote: true;
tables: LateTables;
appToken: string;
srcDir: URL;
@@ -71,8 +71,8 @@ export function vitePluginDb(params: VitePluginDBParams): VitePlugin {
async load(id) {
if (id !== resolved.module && id !== resolved.importedFromSeedFile) return;
- if (params.connectToStudio) {
- return getStudioVirtualModContents({
+ if (params.connectToRemote) {
+ return getRemoteVirtualModContents({
appToken: params.appToken,
tables: params.tables.get(),
isBuild: command === 'build',
@@ -138,7 +138,7 @@ export * from ${RUNTIME_VIRTUAL_IMPORT};
${getStringifiedTableExports(tables)}`;
}
-export function getStudioVirtualModContents({
+export function getRemoteVirtualModContents({
tables,
appToken,
isBuild,
@@ -153,13 +153,12 @@ export function getStudioVirtualModContents({
function appTokenArg() {
if (isBuild) {
- const envPrefix = dbInfo.type === 'studio' ? 'ASTRO_STUDIO' : 'ASTRO_DB';
if (output === 'server') {
// In production build, always read the runtime environment variable.
- return `process.env.${envPrefix}_APP_TOKEN`;
+ return `process.env.ASTRO_DB_APP_TOKEN`;
} else {
// Static mode or prerendering needs the local app token.
- return `process.env.${envPrefix}_APP_TOKEN ?? ${JSON.stringify(appToken)}`;
+ return `process.env.ASTRO_DB_APP_TOKEN ?? ${JSON.stringify(appToken)}`;
}
} else {
return JSON.stringify(appToken);
@@ -171,9 +170,7 @@ export function getStudioVirtualModContents({
if (isBuild) {
// Allow overriding, mostly for testing
- return dbInfo.type === 'studio'
- ? `import.meta.env.ASTRO_STUDIO_REMOTE_DB_URL ?? ${dbStr}`
- : `import.meta.env.ASTRO_DB_REMOTE_URL ?? ${dbStr}`;
+ return `import.meta.env.ASTRO_DB_REMOTE_URL ?? ${dbStr}`;
} else {
return dbStr;
}
diff --git a/packages/db/src/core/load-file.ts b/packages/db/src/core/load-file.ts
index 027deaa60..f7a4226b6 100644
--- a/packages/db/src/core/load-file.ts
+++ b/packages/db/src/core/load-file.ts
@@ -144,8 +144,8 @@ export async function bundleFile({
format: 'esm',
sourcemap: 'inline',
metafile: true,
+ // TODO: use astro:env
define: {
- 'import.meta.env.ASTRO_STUDIO_REMOTE_DB_URL': 'undefined',
'import.meta.env.ASTRO_DB_REMOTE_DB_URL': 'undefined',
'import.meta.env.ASTRO_DATABASE_FILE': JSON.stringify(ASTRO_DATABASE_FILE ?? ''),
},
diff --git a/packages/db/src/core/schemas.ts b/packages/db/src/core/schemas.ts
index c9575a79a..1147538ce 100644
--- a/packages/db/src/core/schemas.ts
+++ b/packages/db/src/core/schemas.ts
@@ -94,7 +94,7 @@ const textColumnBaseSchema = baseColumnSchema
z.object({
// text primary key allows NULL values.
// NULL values bypass unique checks, which could
- // lead to duplicate URLs per record in Astro Studio.
+ // lead to duplicate URLs per record.
// disable `optional` for primary keys.
primaryKey: z.literal(true),
optional: z.literal(false).optional(),
diff --git a/packages/db/src/core/utils.ts b/packages/db/src/core/utils.ts
index b246997e2..3348e90a0 100644
--- a/packages/db/src/core/utils.ts
+++ b/packages/db/src/core/utils.ts
@@ -1,4 +1,3 @@
-import { type ManagedAppToken, getAstroStudioEnv, getManagedAppTokenOrExit } from '@astrojs/studio';
import type { AstroConfig, AstroIntegration } from 'astro';
import { loadEnv } from 'vite';
import './types.js';
@@ -11,49 +10,23 @@ export function getAstroEnv(envMode = ''): Record<`ASTRO_${string}`, string> {
}
export type RemoteDatabaseInfo = {
- type: 'libsql' | 'studio';
+ type: 'libsql';
url: string;
};
export function getRemoteDatabaseInfo(): RemoteDatabaseInfo {
const astroEnv = getAstroEnv();
- const studioEnv = getAstroStudioEnv();
-
- if (studioEnv.ASTRO_STUDIO_REMOTE_DB_URL)
- return {
- type: 'studio',
- url: studioEnv.ASTRO_STUDIO_REMOTE_DB_URL,
- };
-
- if (astroEnv.ASTRO_DB_REMOTE_URL)
- return {
- type: 'libsql',
- url: astroEnv.ASTRO_DB_REMOTE_URL,
- };
return {
- type: 'studio',
- url: 'https://db.services.astro.build',
+ type: 'libsql',
+ url: astroEnv.ASTRO_DB_REMOTE_URL,
};
}
-export function getManagedRemoteToken(
- token?: string,
- dbInfo?: RemoteDatabaseInfo,
-): Promise<ManagedAppToken> {
- dbInfo ??= getRemoteDatabaseInfo();
-
- if (dbInfo.type === 'studio') {
- return getManagedAppTokenOrExit(token);
- }
-
+export function getManagedRemoteToken(token?: string): string {
const astroEnv = getAstroEnv();
- return Promise.resolve({
- token: token ?? astroEnv.ASTRO_DB_APP_TOKEN,
- renew: () => Promise.resolve(),
- destroy: () => Promise.resolve(),
- });
+ return token ?? astroEnv.ASTRO_DB_APP_TOKEN;
}
export function getDbDirectoryUrl(root: URL | string) {
diff --git a/packages/db/src/runtime/db-client.ts b/packages/db/src/runtime/db-client.ts
index 55288951d..e45a2d717 100644
--- a/packages/db/src/runtime/db-client.ts
+++ b/packages/db/src/runtime/db-client.ts
@@ -1,10 +1,7 @@
-import type { InStatement } from '@libsql/client';
import { type Config as LibSQLConfig, createClient } from '@libsql/client';
import type { LibSQLDatabase } from 'drizzle-orm/libsql';
import { drizzle as drizzleLibsql } from 'drizzle-orm/libsql';
-import { type SqliteRemoteDatabase, drizzle as drizzleProxy } from 'drizzle-orm/sqlite-proxy';
-import { z } from 'zod';
-import { DetailedLibsqlError, safeFetch } from './utils.js';
+import type { SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy';
const isWebContainer = !!process.versions?.webcontainer;
@@ -34,16 +31,8 @@ export function createLocalDatabaseClient(options: LocalDbClientOptions): LibSQL
return db;
}
-const remoteResultSchema = z.object({
- columns: z.array(z.string()),
- columnTypes: z.array(z.string()),
- rows: z.array(z.array(z.unknown())),
- rowsAffected: z.number(),
- lastInsertRowid: z.unknown().optional(),
-});
-
type RemoteDbClientOptions = {
- dbType: 'studio' | 'libsql';
+ dbType: 'libsql';
appToken: string;
remoteUrl: string | URL;
};
@@ -51,9 +40,7 @@ type RemoteDbClientOptions = {
export function createRemoteDatabaseClient(options: RemoteDbClientOptions) {
const remoteUrl = new URL(options.remoteUrl);
- return options.dbType === 'studio'
- ? createStudioDatabaseClient(options.appToken, remoteUrl)
- : createRemoteLibSQLClient(options.appToken, remoteUrl, options.remoteUrl.toString());
+ return createRemoteLibSQLClient(options.appToken, remoteUrl, options.remoteUrl.toString());
}
// this function parses the options from a `Record<string, string>`
@@ -99,167 +86,3 @@ function createRemoteLibSQLClient(appToken: string, remoteDbURL: URL, rawUrl: st
const client = createClient({ ...parseOpts(options), url, authToken: appToken });
return drizzleLibsql(client);
}
-
-function createStudioDatabaseClient(appToken: string, remoteDbURL: URL) {
- if (appToken == null) {
- throw new Error(`Cannot create a remote client: missing app token.`);
- }
-
- const url = new URL('/db/query', remoteDbURL);
-
- const db = drizzleProxy(
- async (sql, parameters, method) => {
- const requestBody: InStatement = { sql, args: parameters };
- const res = await safeFetch(
- url,
- {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${appToken}`,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(requestBody),
- },
- async (response) => {
- throw await parseRemoteError(response);
- },
- );
-
- let remoteResult: z.infer<typeof remoteResultSchema>;
- try {
- const json = await res.json();
- remoteResult = remoteResultSchema.parse(json);
- } catch {
- throw new DetailedLibsqlError({
- message: await getUnexpectedResponseMessage(res),
- code: KNOWN_ERROR_CODES.SQL_QUERY_FAILED,
- });
- }
-
- if (method === 'run') {
- const rawRows = Array.from(remoteResult.rows);
- // Implement basic `toJSON()` for Drizzle to serialize properly
- (remoteResult as any).rows.toJSON = () => rawRows;
- // Using `db.run()` drizzle massages the rows into an object.
- // So in order to make dev/prod consistent, we need to do the same here.
- // This creates an object and loops over each row replacing it with the object.
- for (let i = 0; i < remoteResult.rows.length; i++) {
- let row = remoteResult.rows[i];
- let item: Record<string, any> = {};
- remoteResult.columns.forEach((col, index) => {
- item[col] = row[index];
- });
- (remoteResult as any).rows[i] = item;
- }
- return remoteResult;
- }
-
- // Drizzle expects each row as an array of its values
- const rowValues: unknown[][] = [];
-
- for (const row of remoteResult.rows) {
- if (row != null && typeof row === 'object') {
- rowValues.push(Object.values(row));
- }
- }
-
- if (method === 'get') {
- return { rows: rowValues[0] };
- }
-
- return { rows: rowValues };
- },
- async (queries) => {
- const stmts: InStatement[] = queries.map(({ sql, params }) => ({ sql, args: params }));
- const res = await safeFetch(
- url,
- {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${appToken}`,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(stmts),
- },
- async (response) => {
- throw await parseRemoteError(response);
- },
- );
-
- let remoteResults: z.infer<typeof remoteResultSchema>[];
- try {
- const json = await res.json();
- remoteResults = z.array(remoteResultSchema).parse(json);
- } catch {
- throw new DetailedLibsqlError({
- message: await getUnexpectedResponseMessage(res),
- code: KNOWN_ERROR_CODES.SQL_QUERY_FAILED,
- });
- }
- let results: any[] = [];
- for (const [idx, rawResult] of remoteResults.entries()) {
- if (queries[idx]?.method === 'run') {
- results.push(rawResult);
- continue;
- }
-
- // Drizzle expects each row as an array of its values
- const rowValues: unknown[][] = [];
-
- for (const row of rawResult.rows) {
- if (row != null && typeof row === 'object') {
- rowValues.push(Object.values(row));
- }
- }
-
- if (queries[idx]?.method === 'get') {
- results.push({ rows: rowValues[0] });
- }
-
- results.push({ rows: rowValues });
- }
- return results;
- },
- );
- applyTransactionNotSupported(db);
- return db;
-}
-
-const errorSchema = z.object({
- success: z.boolean(),
- error: z.object({
- code: z.string(),
- details: z.string().optional(),
- }),
-});
-
-const KNOWN_ERROR_CODES = {
- SQL_QUERY_FAILED: 'SQL_QUERY_FAILED',
-};
-
-const getUnexpectedResponseMessage = async (response: Response) =>
- `Unexpected response from remote database:\n(Status ${response.status}) ${await response
- .clone()
- .text()}`;
-
-async function parseRemoteError(response: Response): Promise<DetailedLibsqlError> {
- let error;
- try {
- error = errorSchema.parse(await response.clone().json()).error;
- } catch {
- return new DetailedLibsqlError({
- message: await getUnexpectedResponseMessage(response),
- code: KNOWN_ERROR_CODES.SQL_QUERY_FAILED,
- });
- }
- // Strip LibSQL error prefixes
- let baseDetails =
- error.details?.replace(/.*SQLite error: /, '') ?? 'Error querying remote database.';
- // Remove duplicated "code" in details
- const details = baseDetails.slice(baseDetails.indexOf(':') + 1).trim();
- let hint = `See the Astro DB guide for query and push instructions: https://docs.astro.build/en/guides/astro-db/#query-your-database`;
- if (error.code === KNOWN_ERROR_CODES.SQL_QUERY_FAILED && details.includes('no such table')) {
- hint = `Did you run \`astro db push\` to push your latest table schemas?`;
- }
- return new DetailedLibsqlError({ message: details, code: error.code, hint });
-}