0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00
This commit is contained in:
Fred K. Schott 2024-01-26 22:35:22 -08:00
parent bbe2adf192
commit 6428abf706
16 changed files with 274 additions and 297 deletions

View file

@ -10,6 +10,9 @@ benchmark/results/
**/vendor
**/.vercel
# Short-term need to format
!packages/db/test/fixtures
# Directories
.github
.changeset

View file

@ -15,7 +15,13 @@ import type {
import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core';
import { customAlphabet } from 'nanoid';
import prompts from 'prompts';
import { getCreateTableQuery, getModifiers, hasDefault, hasPrimaryKey, schemaTypeToSqlType } from '../internal.js';
import {
getCreateTableQuery,
getModifiers,
hasDefault,
hasPrimaryKey,
schemaTypeToSqlType,
} from '../internal.js';
const sqlite = new SQLiteAsyncDialect();
const genTempTableName = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10);
@ -157,15 +163,10 @@ export async function getCollectionChangeQueries({
}
}
const addedPrimaryKey = Object.entries(added).find(
([, field]) => hasPrimaryKey(field)
);
const droppedPrimaryKey = Object.entries(dropped).find(
([, field]) => hasPrimaryKey(field)
);
const addedPrimaryKey = Object.entries(added).find(([, field]) => hasPrimaryKey(field));
const droppedPrimaryKey = Object.entries(dropped).find(([, field]) => hasPrimaryKey(field));
const updatedPrimaryKey = Object.entries(updated).find(
([, field]) =>
(hasPrimaryKey(field.old) || hasPrimaryKey(field.new))
([, field]) => hasPrimaryKey(field.old) || hasPrimaryKey(field.new)
);
const recreateTableQueries = getRecreateTableQueries({
unescapedCollectionName: collectionName,
@ -425,10 +426,7 @@ function canAlterTableDropColumn(field: DBField) {
return true;
}
type DataLossReason =
| 'added-required'
| 'added-unique'
| 'updated-type';
type DataLossReason = 'added-required' | 'added-unique' | 'updated-type';
type DataLossResponse =
| { dataLoss: false }
| { dataLoss: true; fieldName: string; reason: DataLossReason };

View file

@ -44,7 +44,7 @@ export function integration(): AstroIntegration {
dbPlugin = vitePluginDb({
connectToStudio: true,
collections,
appToken
appToken,
});
} else {
const dbUrl = getLocalDbUrl(config.root);

View file

@ -5,7 +5,7 @@ import { DB_PATH } from './consts.js';
export function findLocalDatabase(localDbURL: string): string {
let dbURL: URL | undefined = undefined;
if(process.env.VERCEL) {
if (process.env.VERCEL) {
dbURL = new URL(DB_PATH, 'file://' + process.cwd() + '/vercel/path0/');
} else {
dbURL = new URL(localDbURL);

View file

@ -36,15 +36,15 @@ export function vitePluginDb(
},
async buildEnd() {
// For local use, emit the database into the output
if('dbUrl' in params) {
if ('dbUrl' in params) {
const data = await fs.promises.readFile(new URL(params.dbUrl));
this.emitFile({
fileName: 'content.db',
source: data,
type: 'asset'
type: 'asset',
});
}
}
},
};
}
@ -61,9 +61,9 @@ import { collectionToTable, createLocalDatabaseClient, findLocalDatabase } from
export const dbUrl = findLocalDatabase(${JSON.stringify(dbUrl)});
const params = ${JSON.stringify({
collections,
seeding: false,
})};
collections,
seeding: false,
})};
params.dbUrl = dbUrl;
export const db = await createLocalDatabaseClient(params);
@ -84,7 +84,9 @@ export function getStudioVirtualModContents({
return `
import {collectionToTable, createRemoteDatabaseClient} from ${INTERNAL_MOD_IMPORT};
export const db = await createRemoteDatabaseClient(${JSON.stringify(appToken)}, import.meta.env.ASTRO_STUDIO_REMOTE_DB_URL);
export const db = await createRemoteDatabaseClient(${JSON.stringify(
appToken
)}, import.meta.env.ASTRO_STUDIO_REMOTE_DB_URL);
export * from ${DRIZZLE_MOD_IMPORT};
${getStringifiedCollectionExports(collections)}

View file

@ -20,21 +20,11 @@ export default defineConfig({
collections: { Author, Themes },
data({ seed }) {
seed(Author, [
{
name: 'Ben',
},
{
name: 'Nate',
},
{
name: 'Erika',
},
{
name: 'Bjorn',
},
{
name: 'Sarah',
},
{ name: 'Ben' },
{ name: 'Nate' },
{ name: 'Erika' },
{ name: 'Bjorn' },
{ name: 'Sarah' },
]);
},
},

View file

@ -5,8 +5,8 @@ const authors = await db.select().from(Author);
let error: any = {};
try {
db.insert(Author).values({ name: 'Person A' })
} catch(err) {
db.insert(Author).values({ name: 'Person A' });
} catch (err) {
error = err;
}
---

View file

@ -4,7 +4,7 @@ import { Themes, db } from 'astro:db';
let error: any = {};
try {
db.insert(Themes).values({ name: 'Person A' });
} catch(err) {
} catch (err) {
error = err;
}
---

View file

@ -1,43 +1,40 @@
// Generated by simple:form
import { type ComponentProps, createContext } from "preact";
import { useContext, useState } from "preact/hooks";
import { navigate } from "astro:transitions/client";
import { type ComponentProps, createContext } from 'preact';
import { useContext, useState } from 'preact/hooks';
import { navigate } from 'astro:transitions/client';
import {
type FieldErrors,
type FormState,
type FormValidator,
formNameInputProps,
getInitialFormState,
toSetValidationErrors,
toTrackAstroSubmitStatus,
toValidateField,
validateForm,
} from "simple:form";
type FieldErrors,
type FormState,
type FormValidator,
formNameInputProps,
getInitialFormState,
toSetValidationErrors,
toTrackAstroSubmitStatus,
toValidateField,
validateForm,
} from 'simple:form';
export function useCreateFormContext(
validator: FormValidator,
fieldErrors?: FieldErrors
) {
const initial = getInitialFormState({ validator, fieldErrors });
const [formState, setFormState] = useState<FormState>(initial);
return {
value: formState,
set: setFormState,
setValidationErrors: toSetValidationErrors(setFormState),
validateField: toValidateField(setFormState),
trackAstroSubmitStatus: toTrackAstroSubmitStatus(setFormState),
};
export function useCreateFormContext(validator: FormValidator, fieldErrors?: FieldErrors) {
const initial = getInitialFormState({ validator, fieldErrors });
const [formState, setFormState] = useState<FormState>(initial);
return {
value: formState,
set: setFormState,
setValidationErrors: toSetValidationErrors(setFormState),
validateField: toValidateField(setFormState),
trackAstroSubmitStatus: toTrackAstroSubmitStatus(setFormState),
};
}
export function useFormContext() {
const formContext = useContext(FormContext);
if (!formContext) {
throw new Error(
"Form context not found. `useFormContext()` should only be called from children of a <Form> component."
);
}
return formContext;
const formContext = useContext(FormContext);
if (!formContext) {
throw new Error(
'Form context not found. `useFormContext()` should only be called from children of a <Form> component.'
);
}
return formContext;
}
type FormContextType = ReturnType<typeof useCreateFormContext>;
@ -45,87 +42,82 @@ type FormContextType = ReturnType<typeof useCreateFormContext>;
const FormContext = createContext<FormContextType | undefined>(undefined);
export function Form({
children,
validator,
context,
fieldErrors,
name,
...formProps
children,
validator,
context,
fieldErrors,
name,
...formProps
}: {
validator: FormValidator;
context?: FormContextType;
fieldErrors?: FieldErrors;
} & Omit<ComponentProps<"form">, "method" | "onSubmit">) {
const formContext = context ?? useCreateFormContext(validator, fieldErrors);
validator: FormValidator;
context?: FormContextType;
fieldErrors?: FieldErrors;
} & Omit<ComponentProps<'form'>, 'method' | 'onSubmit'>) {
const formContext = context ?? useCreateFormContext(validator, fieldErrors);
return (
<FormContext.Provider value={formContext}>
<form
{...formProps}
method="POST"
onSubmit={async (e) => {
e.preventDefault();
e.stopPropagation();
const formData = new FormData(e.currentTarget);
formContext.set((formState) => ({
...formState,
isSubmitPending: true,
submitStatus: "validating",
}));
const parsed = await validateForm({ formData, validator });
if (parsed.data) {
const action =
typeof formProps.action === "string"
? formProps.action
: // Check for Preact signals
formProps.action?.value ?? "";
navigate(action, { formData });
return formContext.trackAstroSubmitStatus();
}
return (
<FormContext.Provider value={formContext}>
<form
{...formProps}
method="POST"
onSubmit={async (e) => {
e.preventDefault();
e.stopPropagation();
const formData = new FormData(e.currentTarget);
formContext.set((formState) => ({
...formState,
isSubmitPending: true,
submitStatus: 'validating',
}));
const parsed = await validateForm({ formData, validator });
if (parsed.data) {
const action =
typeof formProps.action === 'string'
? formProps.action
: // Check for Preact signals
formProps.action?.value ?? '';
navigate(action, { formData });
return formContext.trackAstroSubmitStatus();
}
formContext.setValidationErrors(parsed.fieldErrors);
}}
>
{name ? <input {...formNameInputProps} value={name} /> : null}
{children}
</form>
</FormContext.Provider>
);
formContext.setValidationErrors(parsed.fieldErrors);
}}
>
{name ? <input {...formNameInputProps} value={name} /> : null}
{children}
</form>
</FormContext.Provider>
);
}
export function Input({
onInput,
...inputProps
}: ComponentProps<"input"> & { name: string }) {
const formContext = useFormContext();
const fieldState = formContext.value.fields[inputProps.name];
if (!fieldState) {
throw new Error(
`Input "${inputProps.name}" not found in form. Did you use the <Form> component?`
);
}
export function Input({ onInput, ...inputProps }: ComponentProps<'input'> & { name: string }) {
const formContext = useFormContext();
const fieldState = formContext.value.fields[inputProps.name];
if (!fieldState) {
throw new Error(
`Input "${inputProps.name}" not found in form. Did you use the <Form> component?`
);
}
const { hasErroredOnce, validationErrors, validator } = fieldState;
return (
<>
<input
onBlur={async (e) => {
const value = e.currentTarget.value;
if (value === "") return;
formContext.validateField(inputProps.name, value, validator);
}}
onInput={async (e) => {
onInput?.(e);
const { hasErroredOnce, validationErrors, validator } = fieldState;
return (
<>
<input
onBlur={async (e) => {
const value = e.currentTarget.value;
if (value === '') return;
formContext.validateField(inputProps.name, value, validator);
}}
onInput={async (e) => {
onInput?.(e);
if (!hasErroredOnce) return;
const value = e.currentTarget.value;
formContext.validateField(inputProps.name, value, validator);
}}
{...inputProps}
/>
{validationErrors?.map((e) => (
<p key={e}>{e}</p>
))}
</>
);
if (!hasErroredOnce) return;
const value = e.currentTarget.value;
formContext.validateField(inputProps.name, value, validator);
}}
{...inputProps}
/>
{validationErrors?.map((e) => <p key={e}>{e}</p>)}
</>
);
}

View file

@ -1,10 +1,10 @@
---
import { ViewTransitions } from "astro:transitions";
import "open-props/normalize";
import "open-props/style";
import { ViewTransitions } from 'astro:transitions';
import 'open-props/normalize';
import 'open-props/style';
interface Props {
title: string;
title: string;
}
const { title } = Astro.props;
@ -12,69 +12,69 @@ const { title } = Astro.props;
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content="Astro description" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<ViewTransitions handleForms />
</head>
<body>
<slot />
<style is:global>
main {
max-width: 600px;
margin: 0 auto;
padding: var(--size-4);
display: flex;
flex-direction: column;
gap: var(--size-4);
}
<head>
<meta charset="UTF-8" />
<meta name="description" content="Astro description" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<ViewTransitions handleForms />
</head>
<body>
<slot />
<style is:global>
main {
max-width: 600px;
margin: 0 auto;
padding: var(--size-4);
display: flex;
flex-direction: column;
gap: var(--size-4);
}
form {
display: flex;
flex-direction: column;
gap: var(--size-2);
margin-bottom: var(--size-4);
background: var(--surface-2);
padding-inline: var(--size-4);
padding-block: var(--size-6);
border-radius: var(--radius-2);
}
form {
display: flex;
flex-direction: column;
gap: var(--size-2);
margin-bottom: var(--size-4);
background: var(--surface-2);
padding-inline: var(--size-4);
padding-block: var(--size-6);
border-radius: var(--radius-2);
}
.error {
color: var(--red-6);
margin-bottom: var(--size-2);
grid-column: 1 / -1;
}
.error {
color: var(--red-6);
margin-bottom: var(--size-2);
grid-column: 1 / -1;
}
form button {
grid-column: 1 / -1;
background: var(--orange-8);
border-radius: var(--radius-2);
padding-block: var(--size-2);
}
form button {
grid-column: 1 / -1;
background: var(--orange-8);
border-radius: var(--radius-2);
padding-block: var(--size-2);
}
.youre-going {
background: var(--surface-2);
padding: var(--size-2);
border-radius: var(--radius-2);
display: flex;
flex-direction: column;
}
.youre-going {
background: var(--surface-2);
padding: var(--size-2);
border-radius: var(--radius-2);
display: flex;
flex-direction: column;
}
h2 {
font-size: var(--font-size-4);
margin-bottom: var(--size-2);
}
h2 {
font-size: var(--font-size-4);
margin-bottom: var(--size-2);
}
.newsletter {
display: flex;
align-items: center;
gap: var(--size-2);
}
</style>
</body>
.newsletter {
display: flex;
align-items: center;
gap: var(--size-2);
}
</style>
</body>
</html>

View file

@ -1,40 +1,40 @@
import { createForm } from "simple:form";
import { Form, Input } from "../../components/Form";
import { z } from "zod";
import { useState } from "preact/hooks";
import { createForm } from 'simple:form';
import { Form, Input } from '../../components/Form';
import { z } from 'zod';
import { useState } from 'preact/hooks';
export const ticketForm = createForm({
email: z.string().email(),
quantity: z.number().max(10),
newsletter: z.boolean(),
email: z.string().email(),
quantity: z.number().max(10),
newsletter: z.boolean(),
});
export function TicketForm({ price }: { price: number }) {
const [quantity, setQuantity] = useState(1);
return (
<>
<Form validator={ticketForm.validator}>
<h3>${(quantity * price) / 100}</h3>
const [quantity, setQuantity] = useState(1);
return (
<>
<Form validator={ticketForm.validator}>
<h3>${(quantity * price) / 100}</h3>
<label for="quantity">Quantity</label>
<Input
id="quantity"
{...ticketForm.inputProps.quantity}
onInput={(e) => {
const value = Number(e.currentTarget.value);
setQuantity(value);
}}
/>
<label for="quantity">Quantity</label>
<Input
id="quantity"
{...ticketForm.inputProps.quantity}
onInput={(e) => {
const value = Number(e.currentTarget.value);
setQuantity(value);
}}
/>
<label for="email">Email</label>
<Input id="email" {...ticketForm.inputProps.email} />
<label for="email">Email</label>
<Input id="email" {...ticketForm.inputProps.email} />
<div class="newsletter">
<Input id="newsletter" {...ticketForm.inputProps.newsletter} />
<label for="newsletter">Hear about the next event in your area</label>
</div>
<button>Buy tickets</button>
</Form>
</>
);
<div class="newsletter">
<Input id="newsletter" {...ticketForm.inputProps.newsletter} />
<label for="newsletter">Hear about the next event in your area</label>
</div>
<button>Buy tickets</button>
</Form>
</>
);
}

View file

@ -1,56 +1,48 @@
---
import { Event, Ticket, db, eq } from "astro:db";
import Layout from "../../layouts/Layout.astro";
import { TicketForm, ticketForm } from "./_Ticket";
import { Event, Ticket, db, eq } from 'astro:db';
import Layout from '../../layouts/Layout.astro';
import { TicketForm, ticketForm } from './_Ticket';
if (!Astro.params.event) return Astro.redirect("/404");
if (!Astro.params.event) return Astro.redirect('/404');
const event = await db
.select()
.from(Event)
.where(eq(Event.id, Astro.params.event))
.get();
const event = await db.select().from(Event).where(eq(Event.id, Astro.params.event)).get();
if (!event) return Astro.redirect("/404");
if (!event) return Astro.redirect('/404');
const res = await Astro.locals.form.getData(ticketForm);
if (res?.data) {
await db.insert(Ticket).values({
eventId: Astro.params.event,
email: res.data.email,
quantity: res.data.quantity,
newsletter: res.data.newsletter,
});
await db.insert(Ticket).values({
eventId: Astro.params.event,
email: res.data.email,
quantity: res.data.quantity,
newsletter: res.data.newsletter,
});
}
const ticket = await db
.select()
.from(Ticket)
.where(eq(Ticket.eventId, Astro.params.event))
.get();
const ticket = await db.select().from(Ticket).where(eq(Ticket.eventId, Astro.params.event)).get();
---
<Layout title="Welcome to Astro.">
<main>
<h1>{event.name}</h1>
<p>
{event.description}
</p>
<main>
<h1>{event.name}</h1>
<p>
{event.description}
</p>
<TicketForm price={event.ticketPrice} client:load />
{
ticket && (
<section class="youre-going">
<h2>You're going 🙌</h2>
<p>
You have purchased {ticket.quantity} tickets for {event.name}!
</p>
<p>
Check <strong>{ticket.email}</strong> for your tickets.
</p>
</section>
)
}
</main>
<TicketForm price={event.ticketPrice} client:load />
{
ticket && (
<section class="youre-going">
<h2>You're going 🙌</h2>
<p>
You have purchased {ticket.quantity} tickets for {event.name}!
</p>
<p>
Check <strong>{ticket.email}</strong> for your tickets.
</p>
</section>
)
}
</main>
</Layout>

View file

@ -1,17 +1,17 @@
---
import { Event, db } from "astro:db";
import { Event, db } from 'astro:db';
const firstEvent = await db.select().from(Event).get();
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Eventbrite</title>
</head>
<body>
<meta http-equiv="refresh" content={`0; url=${firstEvent!.id}`} />
</body>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Eventbrite</title>
</head>
<body>
<meta http-equiv="refresh" content={`0; url=${firstEvent!.id}`} />
</body>
</html>