0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-10 23:01:26 -05:00

feat: use studio connection for studio builds

This commit is contained in:
bholmesdev 2024-01-16 17:13:05 -05:00 committed by Nate Moore
parent 9119753336
commit 4a51522beb
7 changed files with 112 additions and 68 deletions

View file

@ -1,6 +1,6 @@
#!/usr/bin/env node
import { red } from 'kleur/colors';
import { astroConfigWithDBValidator } from '../config.js';
import { astroConfigWithDbSchema } from '../config.js';
import { errorMap } from '../error-map.js';
import { loadAstroConfig } from '../load-astro-config.js';
@ -26,14 +26,14 @@ async function main() {
async function getAstroConfigOrExit(root: string = process.cwd()) {
const astroConfig = await loadAstroConfig(root);
const parsed = astroConfigWithDBValidator.safeParse(astroConfig, { errorMap });
const parsed = astroConfigWithDbSchema.safeParse(astroConfig, { errorMap });
if (parsed.success) {
return parsed.data;
}
// eslint-disable-next-line no-console
console.error(
red('⚠️ Invalid studio config. Check your astro config file\n') +
parsed.error.issues.map((i) => i.message).join('\n'),
parsed.error.issues.map((i) => i.message).join('\n')
);
process.exit(0);
}

View file

@ -10,32 +10,40 @@ import {
} from './types.js';
import { z } from 'zod';
export const adjustedConfigSchema = z.object({
export const dbConfigSchema = z.object({
studio: z.boolean().optional(),
collections: collectionsSchema.optional(),
});
export type DBUserConfig = z.input<typeof adjustedConfigSchema>;
export type DBUserConfig = z.input<typeof dbConfigSchema>;
export const astroConfigWithDBValidator = z.object({
db: adjustedConfigSchema.optional(),
export const astroConfigWithDbSchema = z.object({
db: dbConfigSchema.optional(),
});
type CollectionConfig<TFields extends z.input<typeof collectionSchema>['fields'], Writable extends boolean> =
Writable extends true ? (
{
type CollectionConfig<
TFields extends z.input<typeof collectionSchema>['fields'],
Writable extends boolean,
> = Writable extends true
? {
fields: TFields;
// TODO: type inference based on field type. Just `any` for now.
seed?: Writable extends false ? never : () => Array<Record<keyof TFields, any> & { id?: string }>;
seed?: Writable extends false
? never
: () => Array<Record<keyof TFields, any> & { id?: string }>;
}
) : (
{
: {
fields: TFields;
// TODO: type inference based on field type. Just `any` for now.
data?: Writable extends true ? never : () => Array<Record<keyof TFields, any> & { id?: string }>;
}
)
data?: Writable extends true
? never
: () => Array<Record<keyof TFields, any> & { id?: string }>;
};
type ResolvedCollectionConfig<TFields extends z.input<typeof collectionSchema>['fields'], Writable extends boolean> = CollectionConfig<TFields, Writable> & {
type ResolvedCollectionConfig<
TFields extends z.input<typeof collectionSchema>['fields'],
Writable extends boolean,
> = CollectionConfig<TFields, Writable> & {
writable: Writable;
};
@ -44,20 +52,20 @@ export function defineCollection<TFields extends z.input<typeof collectionSchema
): ResolvedCollectionConfig<TFields, false> {
return {
...userConfig,
writable: false
writable: false,
};
}
export function defineWritableCollection<TFields extends z.input<typeof collectionSchema>['fields']>(
userConfig: CollectionConfig<TFields, true>
): ResolvedCollectionConfig<TFields, true> {
export function defineWritableCollection<
TFields extends z.input<typeof collectionSchema>['fields'],
>(userConfig: CollectionConfig<TFields, true>): ResolvedCollectionConfig<TFields, true> {
return {
...userConfig,
writable: true
writable: true,
};
}
export type AstroConfigWithDB = z.infer<typeof astroConfigWithDBValidator>;
export type AstroConfigWithDB = z.infer<typeof astroConfigWithDbSchema>;
type FieldOpts<T extends DBFieldInput> = Omit<T, 'type'>;

View file

@ -1,4 +1,3 @@
import { readFileSync } from 'node:fs';
export const PACKAGE_NAME = JSON.parse(
@ -13,6 +12,6 @@ export const DB_TYPES_FILE = 'db-types.d.ts';
export const VIRTUAL_MODULE_ID = 'astro:db';
// TODO: copy DB to build for serverless
export function getDbUrl(root: URL) {
export function getLocalDbUrl(root: URL) {
return new URL('.astro/content.db', root);
}

View file

@ -2,11 +2,13 @@ import type { AstroIntegration } from 'astro';
import { vitePluginDb } from './vite-plugin-db.js';
import { vitePluginInjectEnvTs } from './vite-plugin-inject-env-ts.js';
import { typegen } from './typegen.js';
import { collectionsSchema } from './types.js';
import { existsSync } from 'fs';
import { rm } from 'fs/promises';
import { getDbUrl } from './consts.js';
import { getLocalDbUrl } from './consts.js';
import { createDb, setupDbTables } from './internal.js';
import { astroConfigWithDbSchema } from './config.js';
import { getAstroStudioEnv, type VitePlugin } from './utils.js';
import { appTokenError } from './errors.js';
export function integration(): AstroIntegration {
return {
@ -17,27 +19,33 @@ export function integration(): AstroIntegration {
// TODO: refine where we load collections
// @matthewp: may want to load collections by path at runtime
const collections = collectionsSchema.parse(config.db?.collections ?? {});
const dbUrl = getDbUrl(config.root);
if (existsSync(dbUrl)) {
await rm(dbUrl);
const configWithDb = astroConfigWithDbSchema.parse(config);
const collections = configWithDb.db?.collections ?? {};
const studio = configWithDb.db?.studio ?? false;
let dbPlugin: VitePlugin;
if (studio && command === 'build') {
const appToken = getAstroStudioEnv().ASTRO_STUDIO_APP_TOKEN;
if (!appToken) {
logger.error(appTokenError);
process.exit(0);
}
dbPlugin = vitePluginDb({ connectToStudio: true, collections, appToken });
} else {
const dbUrl = getLocalDbUrl(config.root).href;
if (existsSync(dbUrl)) {
await rm(dbUrl);
}
const db = await createDb({ collections, dbUrl, seeding: true });
await setupDbTables({ db, collections, logger });
logger.info('Collections set up 🚀');
dbPlugin = vitePluginDb({ connectToStudio: false, collections, dbUrl });
}
const db = await createDb({ collections, dbUrl: dbUrl.href, seeding: true });
await setupDbTables({ db, collections, logger });
logger.info('Collections set up 🚀');
updateConfig({
vite: {
plugins: [
// TODO: figure out when vite.Plugin doesn't line up with these types
// @ts-ignore
vitePluginDb({
collections,
root: config.root,
}),
// @ts-ignore
vitePluginInjectEnvTs(config),
],
plugins: [dbPlugin, vitePluginInjectEnvTs(config)],
},
});
await typegen({ collections, root: config.root });

View file

@ -2,7 +2,6 @@ import type { SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy';
import { createClient } from '@libsql/client';
import {
type ReadableDBCollection,
type WritableDBCollection,
type BooleanField,
type DBCollection,
type DBCollections,
@ -49,12 +48,18 @@ function checkIfModificationIsAllowed(collections: DBCollections, Table: SQLiteT
// This totally works, don't worry about it
const tableName = (Table as any)[(SQLiteTable as any).Symbol.Name];
const collection = collections[tableName];
if(collection.writable) {
if (collection.writable) {
throw new Error(`The [${tableName}] collection is read-only.`);
}
}
export async function createDb({ collections, dbUrl, seeding }: {
export { createDb as createStudioDb } from './cli/sync/remote-db.js';
export async function createDb({
collections,
dbUrl,
seeding,
}: {
dbUrl: string;
collections: DBCollections;
seeding: boolean;
@ -62,13 +67,9 @@ export async function createDb({ collections, dbUrl, seeding }: {
const client = createClient({ url: dbUrl });
const db = drizzle(client);
if(seeding) return db;
if (seeding) return db;
const {
insert: drizzleInsert,
update: drizzleUpdate,
delete: drizzleDelete
} = db;
const { insert: drizzleInsert, update: drizzleUpdate, delete: drizzleDelete } = db;
return Object.assign(db, {
insert(Table: SQLiteTable) {
//console.log('Table info...', Table._);

View file

@ -1,17 +1,22 @@
import { existsSync } from 'node:fs';
import { DRIZZLE_MOD_IMPORT, INTERNAL_MOD_IMPORT, VIRTUAL_MODULE_ID, getDbUrl } from './consts.js';
import { DRIZZLE_MOD_IMPORT, INTERNAL_MOD_IMPORT, VIRTUAL_MODULE_ID } from './consts.js';
import type { DBCollections } from './types.js';
import type { Plugin as VitePlugin } from 'vite';
import type { VitePlugin } from './utils.js';
const resolvedVirtualModuleId = '\0' + VIRTUAL_MODULE_ID;
export function vitePluginDb({
collections,
root,
}: {
collections: DBCollections;
root: URL;
}): VitePlugin {
export function vitePluginDb(
params:
| {
connectToStudio: false;
collections: DBCollections;
dbUrl: string;
}
| {
connectToStudio: true;
collections: DBCollections;
appToken: string;
}
): VitePlugin {
return {
name: 'astro:db',
enforce: 'pre',
@ -22,19 +27,22 @@ export function vitePluginDb({
},
load(id) {
if (id !== resolvedVirtualModuleId) return;
return getVirtualModContents({ collections, root });
if (params.connectToStudio) {
return getStudioVirtualModContents(params);
}
return getVirtualModContents(params);
},
};
}
export function getVirtualModContents({
collections,
root,
dbUrl,
}: {
collections: DBCollections;
root: URL;
dbUrl: string;
}) {
const dbUrl = getDbUrl(root).href;
return `
import { collectionToTable, createDb } from ${INTERNAL_MOD_IMPORT};
@ -43,12 +51,32 @@ export const db = await createDb(${JSON.stringify({
dbUrl,
seeding: false,
})});
export * from ${DRIZZLE_MOD_IMPORT};
${getStringifiedCollectionExports(collections)}
`;
}
export function getStudioVirtualModContents({
collections,
appToken,
}: {
collections: DBCollections;
appToken: string;
}) {
return `
import {collectionToTable, createStudioDb} from ${INTERNAL_MOD_IMPORT};
export const db = await createStudioDb(${JSON.stringify({
appToken,
})});
export * from ${DRIZZLE_MOD_IMPORT};
${getStringifiedCollectionExports(collections)}
`;
}
function getStringifiedCollectionExports(collections: DBCollections) {
return Object.entries(collections)
.map(

View file

@ -4,8 +4,8 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { bold, cyan } from 'kleur/colors';
import { normalizePath } from 'vite';
import type { Plugin as VitePlugin } from 'vite';
import { DB_TYPES_FILE } from './consts.js';
import type { VitePlugin } from './utils.js';
export function vitePluginInjectEnvTs({ srcDir, root }: { srcDir: URL; root: URL }): VitePlugin {
return {