From 08ba552672937066d30ef3938fa4495e59e8a393 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Thu, 25 Apr 2024 19:55:08 +0200 Subject: [PATCH 01/29] Init web-vitals integration --- packages/integrations/web-vitals/README.md | 39 ++++++++++++ packages/integrations/web-vitals/package.json | 63 +++++++++++++++++++ .../web-vitals/src/client-script.ts | 36 +++++++++++ .../integrations/web-vitals/src/db-config.ts | 19 ++++++ .../integrations/web-vitals/src/endpoint.ts | 19 ++++++ packages/integrations/web-vitals/src/index.ts | 33 ++++++++++ .../integrations/web-vitals/src/middleware.ts | 60 ++++++++++++++++++ .../integrations/web-vitals/src/schemas.ts | 32 ++++++++++ .../integrations/web-vitals/tsconfig.json | 7 +++ pnpm-lock.yaml | 16 +++++ 10 files changed, 324 insertions(+) create mode 100644 packages/integrations/web-vitals/README.md create mode 100644 packages/integrations/web-vitals/package.json create mode 100644 packages/integrations/web-vitals/src/client-script.ts create mode 100644 packages/integrations/web-vitals/src/db-config.ts create mode 100644 packages/integrations/web-vitals/src/endpoint.ts create mode 100644 packages/integrations/web-vitals/src/index.ts create mode 100644 packages/integrations/web-vitals/src/middleware.ts create mode 100644 packages/integrations/web-vitals/src/schemas.ts create mode 100644 packages/integrations/web-vitals/tsconfig.json diff --git a/packages/integrations/web-vitals/README.md b/packages/integrations/web-vitals/README.md new file mode 100644 index 000000000000..644b3b19d218 --- /dev/null +++ b/packages/integrations/web-vitals/README.md @@ -0,0 +1,39 @@ +# @astrojs/web-vitals ⏱️ + +This **[Astro integration][astro-integration]** enables tracking real-world website performance and storing the data in [Astro DB][db]. + + + +## Support + +- Get help in the [Astro Discord][discord]. Post questions in our `#support` forum, or visit our dedicated `#dev` channel to discuss current development and more! + +- Check our [Astro Integration Documentation][astro-integration] for more on integrations. + +- Submit bug reports and feature requests as [GitHub issues][issues]. + +## Contributing + +This package is maintained by Astro's Core team. You're welcome to submit an issue or PR! These links will help you get started: + +- [Contributor Manual][contributing] +- [Code of Conduct][coc] +- [Community Guide][community] + +## License + +MIT + +Copyright (c) 2023–present [Astro][astro] + +[astro]: https://astro.build/ +[db]: https://astro.build/db/ +[docs]: https://docs.astro.build/en/guides/integrations-guide/web-vitals/ +[contributing]: https://github.com/withastro/astro/blob/main/CONTRIBUTING.md +[coc]: https://github.com/withastro/.github/blob/main/CODE_OF_CONDUCT.md +[community]: https://github.com/withastro/.github/blob/main/COMMUNITY_GUIDE.md +[discord]: https://astro.build/chat/ +[issues]: https://github.com/withastro/astro/issues +[astro-integration]: https://docs.astro.build/en/guides/integrations-guide/ diff --git a/packages/integrations/web-vitals/package.json b/packages/integrations/web-vitals/package.json new file mode 100644 index 000000000000..a494c590e6ad --- /dev/null +++ b/packages/integrations/web-vitals/package.json @@ -0,0 +1,63 @@ +{ + "name": "@astrojs/web-vitals", + "description": "Track your website’s performance with Astro DB", + "version": "0.0.0", + "type": "module", + "author": "withastro", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/withastro/astro.git", + "directory": "packages/integrations/web-vitals" + }, + "keywords": [ + "withastro", + "astro-integration" + ], + "bugs": "https://github.com/withastro/astro/issues", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./middleware": { + "types": "./dist/middleware.d.ts", + "import": "./dist/middleware.js" + }, + "./endpoint": { + "types": "./dist/endpoint.d.ts", + "import": "./dist/endpoint.js" + }, + "./client-script": { + "types": "./dist/client-script.d.ts", + "import": "./dist/client-script.js" + }, + "./db-config": { + "types": "./dist/db-config.d.ts", + "import": "./dist/db-config.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "astro-scripts build \"src/**/*.ts\" && tsc", + "build:ci": "astro-scripts build \"src/**/*.ts\"", + "dev": "astro-scripts dev \"src/**/*.ts\"", + "test": "astro-scripts test --timeout 50000 \"test/**/*.test.js\"" + }, + "dependencies": { + "web-vitals": "^3.5.2" + }, + "peerDependencies": { + "@astrojs/db": "^0.10.6" + }, + "devDependencies": { + "@astrojs/db": "workspace:*", + "astro": "workspace:*", + "astro-scripts": "workspace:*" + }, + "publishConfig": { + "provenance": true + } +} diff --git a/packages/integrations/web-vitals/src/client-script.ts b/packages/integrations/web-vitals/src/client-script.ts new file mode 100644 index 000000000000..0d88363695ba --- /dev/null +++ b/packages/integrations/web-vitals/src/client-script.ts @@ -0,0 +1,36 @@ +import { onCLS, onINP, onLCP, onFID, onFCP, onTTFB, type Metric } from 'web-vitals'; +import type { ClientMetric } from './schemas.js'; + +const pathname = location.pathname.replace(/(?<=.)\/$/, ''); +const route = + document + .querySelector('meta[name="x-astro-vitals-route"]') + ?.getAttribute('content') || pathname; + +const queue = new Set(); +const addToQueue = (metric: Metric) => queue.add(metric); +function flushQueue() { + if (!queue.size) return; + const rawBody: ClientMetric[] = [...queue].map(({ name, id, value, rating }) => ({ + pathname, + route, + name, + id, + value, + rating, + })); + const body = JSON.stringify(rawBody); + const endpoint = '/_/astro-vitals'; + if (navigator.sendBeacon) navigator.sendBeacon(endpoint, body); + else fetch(endpoint, { body, method: 'POST', keepalive: true }); + queue.clear(); +} + +for (const listener of [onCLS, onLCP, onINP, onFID, onFCP, onTTFB]) { + listener(addToQueue); +} + +addEventListener('visibilitychange', () => { + if (document.visibilityState === 'hidden') flushQueue(); +}); +addEventListener('pagehide', flushQueue); diff --git a/packages/integrations/web-vitals/src/db-config.ts b/packages/integrations/web-vitals/src/db-config.ts new file mode 100644 index 000000000000..cc3407c4b8d5 --- /dev/null +++ b/packages/integrations/web-vitals/src/db-config.ts @@ -0,0 +1,19 @@ +import { column, defineDb, defineTable } from 'astro:db'; + +export const AstrojsWebVitals_Metric = defineTable({ + columns: { + pathname: column.text(), + route: column.text(), + name: column.text(), + id: column.text({ primaryKey: true }), + value: column.number(), + rating: column.text(), + timestamp: column.date(), + }, +}); + +export default defineDb({ + tables: { + AstrojsWebVitals_Metric, + }, +}); diff --git a/packages/integrations/web-vitals/src/endpoint.ts b/packages/integrations/web-vitals/src/endpoint.ts new file mode 100644 index 000000000000..e39a732429b3 --- /dev/null +++ b/packages/integrations/web-vitals/src/endpoint.ts @@ -0,0 +1,19 @@ +import type { APIRoute } from 'astro'; +import { db, AstrojsWebVitals_Metric as Metric, sql } from 'astro:db'; +import { ServerMetricSchema } from './schemas.js'; + +export const prerender = false; + +export const ALL: APIRoute = async ({ request }) => { + try { + const rawBody = await request.json(); + const body = ServerMetricSchema.array().parse(rawBody); + await db + .insert(Metric) + .values(body) + .onConflictDoUpdate({ target: Metric.id, set: { value: sql`excluded.value` } }); + } catch (error) { + console.error(error); + } + return new Response(); +}; diff --git a/packages/integrations/web-vitals/src/index.ts b/packages/integrations/web-vitals/src/index.ts new file mode 100644 index 000000000000..d9ec6fbdb28c --- /dev/null +++ b/packages/integrations/web-vitals/src/index.ts @@ -0,0 +1,33 @@ +import { defineDbIntegration } from '@astrojs/db/utils'; +import { AstroError } from 'astro/errors'; + +export default function webVitals() { + return defineDbIntegration({ + name: '@astrojs/web-vitals', + hooks: { + 'astro:db:setup'({ extendDb }) { + extendDb({ configEntrypoint: '@astrojs/web-vitals/db-config' }); + }, + + 'astro:config:setup'({ addMiddleware, config, injectRoute, injectScript }) { + if (!config.integrations.find(({ name }) => name === 'astro:db')) { + throw new AstroError( + 'Astro DB integration not found.', + 'Run `npx astro add db` to install `@astrojs/db` and add it to your Astro config.' + ); + } + + // Middleware that adds a `` tag to each page. + addMiddleware({ entrypoint: '@astrojs/web-vitals/middleware', order: 'post' }); + // Endpoint that collects metrics and inserts them in Astro DB. + injectRoute({ + entrypoint: '@astrojs/web-vitals/endpoint', + pattern: '/_/astro-vitals', + prerender: false, + }); + // Client-side performance measurement script. + injectScript('page', `import '@astrojs/web-vitals/client-script';`); + }, + }, + }); +} diff --git a/packages/integrations/web-vitals/src/middleware.ts b/packages/integrations/web-vitals/src/middleware.ts new file mode 100644 index 000000000000..94c9b1d3e569 --- /dev/null +++ b/packages/integrations/web-vitals/src/middleware.ts @@ -0,0 +1,60 @@ +import type { MiddlewareHandler } from "astro"; + +/** + * Middleware which adds the web vitals `` tag to each page’s ``. + * + * @example + * + */ +export const onRequest: MiddlewareHandler = async ({ params, url }, next) => { + const response = await next(); + const contentType = response.headers.get('Content-Type'); + if (contentType !== 'text/html') return response; + const webVitalsMetaTag = getMetaTag(url, params); + return new Response( + response.body + ?.pipeThrough(new TextDecoderStream()) + .pipeThrough(HeadInjectionTransformStream(webVitalsMetaTag)) + .pipeThrough(new TextEncoderStream()), + response + ); +}; + +/** TransformStream which injects the passed HTML just before the closing tag. */ +function HeadInjectionTransformStream(htmlToInject: string) { + let hasInjected = false; + return new TransformStream({ + transform: (chunk, controller) => { + if (!hasInjected) { + const headCloseIndex = chunk.indexOf(''); + if (headCloseIndex > -1) { + chunk = chunk.slice(0, headCloseIndex) + htmlToInject + chunk.slice(headCloseIndex); + hasInjected = true; + } + } + controller.enqueue(chunk); + }, + }); +} + +/** Get a `` tag to identify the current Astro route. */ +function getMetaTag(url: URL, params: Record) { + let route = url.pathname; + for (const [key, value] of Object.entries(params)) { + if (value) route = route.replace(value, `[${key}]`); + } + route = miniEncodeAttribute(stripTrailingSlash(route)); + return ``; +} + +function stripTrailingSlash(str: string) { + return str.length > 1 && str.at(-1) === '/' ? str.slice(0, -1) : str; +} + +function miniEncodeAttribute(str: string) { + return str + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"'); +} diff --git a/packages/integrations/web-vitals/src/schemas.ts b/packages/integrations/web-vitals/src/schemas.ts new file mode 100644 index 000000000000..7a2050bd53ae --- /dev/null +++ b/packages/integrations/web-vitals/src/schemas.ts @@ -0,0 +1,32 @@ +import { z } from 'astro/zod'; + +export const RatingSchema = z.enum(['good', 'needs-improvement', 'poor']); +const MetricTypeSchema = z.enum(['CLS', 'INP', 'LCP', 'FCP', 'FID', 'TTFB']); + +/** `web-vitals` generated ID, transformed to reduce data resolution. */ +const MetricIdSchema = z + .string() + // Match https://github.com/GoogleChrome/web-vitals/blob/main/src/lib/generateUniqueID.ts + .regex(/^v3-\d{13}-\d{13}$/) + // Avoid collecting higher resolution timestamp in ID. + // Transforms `'v3-1711484350895-3748043125387'` to `'v3-17114843-3748043125387'` + .transform((id) => id.replace(/^(v3-\d{8})\d{5}(-\d{13})$/, '$1$2')); + +/** Shape of the data submitted from clients to the collection API. */ +const ClientMetricSchema = z.object({ + pathname: z.string(), + route: z.string(), + name: MetricTypeSchema, + id: MetricIdSchema, + value: z.number().gte(0), + rating: RatingSchema, +}); + +/** Transformed client data with added timestamp. */ +export const ServerMetricSchema = ClientMetricSchema.transform((metric) => { + const timestamp = new Date(); + timestamp.setMinutes(0, 0, 0); + return { ...metric, timestamp }; +}); + +export type ClientMetric = z.input; diff --git a/packages/integrations/web-vitals/tsconfig.json b/packages/integrations/web-vitals/tsconfig.json new file mode 100644 index 000000000000..1504b4b6dfa4 --- /dev/null +++ b/packages/integrations/web-vitals/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "outDir": "./dist" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a3de03afbca..c680f58a0057 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5324,6 +5324,22 @@ importers: specifier: workspace:* version: link:../../../../../astro + packages/integrations/web-vitals: + dependencies: + web-vitals: + specifier: ^3.5.2 + version: 3.5.2 + devDependencies: + '@astrojs/db': + specifier: workspace:* + version: link:../../db + astro: + specifier: workspace:* + version: link:../../astro + astro-scripts: + specifier: workspace:* + version: link:../../../scripts + packages/internal-helpers: devDependencies: astro-scripts: From dbb5f7b3beb23c190a95f02f44a9682e7f295b22 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Thu, 25 Apr 2024 22:32:17 +0200 Subject: [PATCH 02/29] Explicitly add DB types to integration env --- packages/integrations/web-vitals/src/env.d.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/integrations/web-vitals/src/env.d.ts diff --git a/packages/integrations/web-vitals/src/env.d.ts b/packages/integrations/web-vitals/src/env.d.ts new file mode 100644 index 000000000000..18ef3554e128 --- /dev/null +++ b/packages/integrations/web-vitals/src/env.d.ts @@ -0,0 +1 @@ +/// From 25edcb7f0008e67b254ababd85d7207fa4a4f663 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Thu, 25 Apr 2024 22:32:43 +0200 Subject: [PATCH 03/29] Refactor endpoint to use `asDrizzleTable()` --- packages/integrations/web-vitals/src/endpoint.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/integrations/web-vitals/src/endpoint.ts b/packages/integrations/web-vitals/src/endpoint.ts index e39a732429b3..4a25d251cb86 100644 --- a/packages/integrations/web-vitals/src/endpoint.ts +++ b/packages/integrations/web-vitals/src/endpoint.ts @@ -1,8 +1,11 @@ +import { asDrizzleTable } from '@astrojs/db/runtime'; import type { APIRoute } from 'astro'; -import { db, AstrojsWebVitals_Metric as Metric, sql } from 'astro:db'; +import { AstrojsWebVitals_Metric } from './db-config.js'; import { ServerMetricSchema } from './schemas.js'; +import { db, sql } from 'astro:db'; export const prerender = false; +const Metric = asDrizzleTable('AstrojsWebVitals_Metric', AstrojsWebVitals_Metric); export const ALL: APIRoute = async ({ request }) => { try { From e93cfe6ffe865953ba6d345e05f8b8df4ac13033 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Thu, 25 Apr 2024 22:33:11 +0200 Subject: [PATCH 04/29] Improve `asDrizzleTable()` types --- packages/db/src/runtime/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/db/src/runtime/index.ts b/packages/db/src/runtime/index.ts index 67dea935af46..c9f1e8444c60 100644 --- a/packages/db/src/runtime/index.ts +++ b/packages/db/src/runtime/index.ts @@ -8,9 +8,10 @@ import { sqliteTable, text, } from 'drizzle-orm/sqlite-core'; -import { type DBColumn, type DBTable } from '../core/types.js'; -import { type SerializedSQL, isSerializedSQL } from './types.js'; +import { type ColumnsConfig, type DBColumn, type DBTable, type TableConfig } from '../core/types.js'; +import { type SerializedSQL, isSerializedSQL, type Table } from './types.js'; import { pathToFileURL } from './utils.js'; +import { tableSchema } from '../core/schemas.js'; export type { Table } from './types.js'; export { createRemoteDatabaseClient, createLocalDatabaseClient } from './db-client.js'; @@ -48,7 +49,8 @@ type D1ColumnBuilder = SQLiteColumnBuilderBase< ColumnBuilderBaseConfig & { data: unknown } >; -export function asDrizzleTable(name: string, table: DBTable) { +export function asDrizzleTable(name: TableName, tbl: TableConfig): Table { + const table = tableSchema.parse(tbl); const columns: Record = {}; if (!Object.entries(table.columns).some(([, column]) => hasPrimaryKey(column))) { columns['_id'] = integer('_id').primaryKey(); @@ -67,7 +69,7 @@ export function asDrizzleTable(name: string, table: DBTable) { } return indexes; }); - return drizzleTable; + return drizzleTable as Table; } function atLeastOne(arr: T[]): arr is [T, ...T[]] { From 715d099cf61f795e1c4308923beba22cf2613e4f Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Thu, 25 Apr 2024 22:35:37 +0200 Subject: [PATCH 05/29] Cast table to `any` to avoid error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inferred type was causing an error which couldn’t be ignored: The inferred type of 'AstrojsWebVitals_Metric' cannot be named without a reference to '../node_modules/@astrojs/db/dist/_internal/core/types.js'. This is likely not portable. A type annotation is necessary. --- packages/integrations/web-vitals/src/db-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integrations/web-vitals/src/db-config.ts b/packages/integrations/web-vitals/src/db-config.ts index cc3407c4b8d5..d1183c6ab17c 100644 --- a/packages/integrations/web-vitals/src/db-config.ts +++ b/packages/integrations/web-vitals/src/db-config.ts @@ -1,6 +1,6 @@ import { column, defineDb, defineTable } from 'astro:db'; -export const AstrojsWebVitals_Metric = defineTable({ +export const AstrojsWebVitals_Metric: any = defineTable({ columns: { pathname: column.text(), route: column.text(), From 8325926a96436bfe0eb7f6441f2c99e494d1a47a Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 01:27:29 +0200 Subject: [PATCH 06/29] Wrap `asDrizzleTable()` for integration utility --- packages/db/src/utils.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/db/src/utils.ts b/packages/db/src/utils.ts index 4e1a18685ebf..7a98cca35cfc 100644 --- a/packages/db/src/utils.ts +++ b/packages/db/src/utils.ts @@ -1,2 +1,11 @@ export { defineDbIntegration } from './core/utils.js'; -export { asDrizzleTable } from './runtime/index.js'; +import { tableSchema } from './core/schemas.js'; +import type { ColumnsConfig, TableConfig } from './core/types.js'; +import { type Table, asDrizzleTable as internal_asDrizzleTable } from './runtime/index.js'; + +export function asDrizzleTable< + TableName extends string = string, + TColumns extends ColumnsConfig = ColumnsConfig, +>(name: TableName, tableConfig: TableConfig) { + return internal_asDrizzleTable(name, tableSchema.parse(tableConfig)) as Table; +} From f1c3fc9a114c84eda0f57a1d55db8d891753759a Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 01:49:40 +0200 Subject: [PATCH 07/29] Revert "Improve `asDrizzleTable()` types" This reverts commit e93cfe6ffe865953ba6d345e05f8b8df4ac13033. --- packages/db/src/runtime/index.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/db/src/runtime/index.ts b/packages/db/src/runtime/index.ts index c9f1e8444c60..67dea935af46 100644 --- a/packages/db/src/runtime/index.ts +++ b/packages/db/src/runtime/index.ts @@ -8,10 +8,9 @@ import { sqliteTable, text, } from 'drizzle-orm/sqlite-core'; -import { type ColumnsConfig, type DBColumn, type DBTable, type TableConfig } from '../core/types.js'; -import { type SerializedSQL, isSerializedSQL, type Table } from './types.js'; +import { type DBColumn, type DBTable } from '../core/types.js'; +import { type SerializedSQL, isSerializedSQL } from './types.js'; import { pathToFileURL } from './utils.js'; -import { tableSchema } from '../core/schemas.js'; export type { Table } from './types.js'; export { createRemoteDatabaseClient, createLocalDatabaseClient } from './db-client.js'; @@ -49,8 +48,7 @@ type D1ColumnBuilder = SQLiteColumnBuilderBase< ColumnBuilderBaseConfig & { data: unknown } >; -export function asDrizzleTable(name: TableName, tbl: TableConfig): Table { - const table = tableSchema.parse(tbl); +export function asDrizzleTable(name: string, table: DBTable) { const columns: Record = {}; if (!Object.entries(table.columns).some(([, column]) => hasPrimaryKey(column))) { columns['_id'] = integer('_id').primaryKey(); @@ -69,7 +67,7 @@ export function asDrizzleTable; + return drizzleTable; } function atLeastOne(arr: T[]): arr is [T, ...T[]] { From 5652b718804c87014f810763a64804ebb8ff416c Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 01:52:20 +0200 Subject: [PATCH 08/29] Fix `asDrizzleTable()` import source --- packages/integrations/web-vitals/src/endpoint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integrations/web-vitals/src/endpoint.ts b/packages/integrations/web-vitals/src/endpoint.ts index 4a25d251cb86..d309927fd5bd 100644 --- a/packages/integrations/web-vitals/src/endpoint.ts +++ b/packages/integrations/web-vitals/src/endpoint.ts @@ -1,4 +1,4 @@ -import { asDrizzleTable } from '@astrojs/db/runtime'; +import { asDrizzleTable } from '@astrojs/db/utils'; import type { APIRoute } from 'astro'; import { AstrojsWebVitals_Metric } from './db-config.js'; import { ServerMetricSchema } from './schemas.js'; From a0e3add68961f4437b2ae26a4f6616c41bd3b40b Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 02:01:14 +0200 Subject: [PATCH 09/29] Refactor to only expose processed table config --- packages/integrations/web-vitals/src/db-config.ts | 7 +++++-- packages/integrations/web-vitals/src/endpoint.ts | 6 ++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/integrations/web-vitals/src/db-config.ts b/packages/integrations/web-vitals/src/db-config.ts index d1183c6ab17c..8405fb223f56 100644 --- a/packages/integrations/web-vitals/src/db-config.ts +++ b/packages/integrations/web-vitals/src/db-config.ts @@ -1,6 +1,7 @@ +import { asDrizzleTable } from '@astrojs/db/utils'; import { column, defineDb, defineTable } from 'astro:db'; -export const AstrojsWebVitals_Metric: any = defineTable({ +const Metric = defineTable({ columns: { pathname: column.text(), route: column.text(), @@ -12,8 +13,10 @@ export const AstrojsWebVitals_Metric: any = defineTable({ }, }); +export const AstrojsWebVitals_Metric = asDrizzleTable('AstrojsWebVitals_Metric', Metric); + export default defineDb({ tables: { - AstrojsWebVitals_Metric, + AstrojsWebVitals_Metric: Metric, }, }); diff --git a/packages/integrations/web-vitals/src/endpoint.ts b/packages/integrations/web-vitals/src/endpoint.ts index d309927fd5bd..bca98ce04088 100644 --- a/packages/integrations/web-vitals/src/endpoint.ts +++ b/packages/integrations/web-vitals/src/endpoint.ts @@ -1,20 +1,18 @@ -import { asDrizzleTable } from '@astrojs/db/utils'; import type { APIRoute } from 'astro'; import { AstrojsWebVitals_Metric } from './db-config.js'; import { ServerMetricSchema } from './schemas.js'; import { db, sql } from 'astro:db'; export const prerender = false; -const Metric = asDrizzleTable('AstrojsWebVitals_Metric', AstrojsWebVitals_Metric); export const ALL: APIRoute = async ({ request }) => { try { const rawBody = await request.json(); const body = ServerMetricSchema.array().parse(rawBody); await db - .insert(Metric) + .insert(AstrojsWebVitals_Metric) .values(body) - .onConflictDoUpdate({ target: Metric.id, set: { value: sql`excluded.value` } }); + .onConflictDoUpdate({ target: AstrojsWebVitals_Metric.id, set: { value: sql`excluded.value` } }); } catch (error) { console.error(error); } From 5da272149c9236f9f1692df19e8cfe434ca8fe9f Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 12:23:21 +0200 Subject: [PATCH 10/29] Add a basic test fixture --- packages/integrations/web-vitals/package.json | 3 ++- .../test/fixtures/basics/astro.config.mjs | 14 ++++++++++++++ .../test/fixtures/basics/package.json | 16 ++++++++++++++++ .../fixtures/basics/src/pages/[dynamic].astro | 19 +++++++++++++++++++ .../fixtures/basics/src/pages/index.astro | 11 +++++++++++ pnpm-lock.yaml | 18 ++++++++++++++++++ 6 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs create mode 100644 packages/integrations/web-vitals/test/fixtures/basics/package.json create mode 100644 packages/integrations/web-vitals/test/fixtures/basics/src/pages/[dynamic].astro create mode 100644 packages/integrations/web-vitals/test/fixtures/basics/src/pages/index.astro diff --git a/packages/integrations/web-vitals/package.json b/packages/integrations/web-vitals/package.json index a494c590e6ad..ca7ef5665d4a 100644 --- a/packages/integrations/web-vitals/package.json +++ b/packages/integrations/web-vitals/package.json @@ -55,7 +55,8 @@ "devDependencies": { "@astrojs/db": "workspace:*", "astro": "workspace:*", - "astro-scripts": "workspace:*" + "astro-scripts": "workspace:*", + "linkedom": "^0.16.11" }, "publishConfig": { "provenance": true diff --git a/packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs b/packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs new file mode 100644 index 000000000000..42bfa6f6693f --- /dev/null +++ b/packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs @@ -0,0 +1,14 @@ +import db from '@astrojs/db'; +import node from '@astrojs/node'; +import webVitals from '@astrojs/web-vitals'; +import { defineConfig } from 'astro/config'; + +// https://astro.build/config +export default defineConfig({ + integrations: [db(), webVitals()], + output: 'hybrid', + adapter: node({ mode: 'standalone' }), + devToolbar: { + enabled: false, + }, +}); diff --git a/packages/integrations/web-vitals/test/fixtures/basics/package.json b/packages/integrations/web-vitals/test/fixtures/basics/package.json new file mode 100644 index 000000000000..25ab0abc1b75 --- /dev/null +++ b/packages/integrations/web-vitals/test/fixtures/basics/package.json @@ -0,0 +1,16 @@ +{ + "name": "@test/web-vitals", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview" + }, + "dependencies": { + "@astrojs/db": "workspace:*", + "@astrojs/node": "workspace:*", + "@astrojs/web-vitals": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/web-vitals/test/fixtures/basics/src/pages/[dynamic].astro b/packages/integrations/web-vitals/test/fixtures/basics/src/pages/[dynamic].astro new file mode 100644 index 000000000000..36c7c50e6276 --- /dev/null +++ b/packages/integrations/web-vitals/test/fixtures/basics/src/pages/[dynamic].astro @@ -0,0 +1,19 @@ +--- +import type { GetStaticPaths } from "astro"; +export const getStaticPaths = (() => { + return [{ params: { dynamic: 'test' } }]; +}) satisfies GetStaticPaths; +--- + + + + + + + Web Vitals basics — dynamic route test + + +

Web Vitals basics

+

Dynamic route test

+ + diff --git a/packages/integrations/web-vitals/test/fixtures/basics/src/pages/index.astro b/packages/integrations/web-vitals/test/fixtures/basics/src/pages/index.astro new file mode 100644 index 000000000000..06ddd6565db9 --- /dev/null +++ b/packages/integrations/web-vitals/test/fixtures/basics/src/pages/index.astro @@ -0,0 +1,11 @@ + + + + + + Web Vitals basics test + + +

Web Vitals basics test

+ + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c680f58a0057..f7383b61e866 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5339,6 +5339,24 @@ importers: astro-scripts: specifier: workspace:* version: link:../../../scripts + linkedom: + specifier: ^0.16.11 + version: 0.16.11 + + packages/integrations/web-vitals/test/fixtures/basics: + dependencies: + '@astrojs/db': + specifier: workspace:* + version: link:../../../../../db + '@astrojs/node': + specifier: workspace:* + version: link:../../../../node + '@astrojs/web-vitals': + specifier: workspace:* + version: link:../../.. + astro: + specifier: workspace:* + version: link:../../../../../astro packages/internal-helpers: devDependencies: From 492c0f3337e40d6e1d75a66066124eae3e359dd9 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 12:23:49 +0200 Subject: [PATCH 11/29] =?UTF-8?q?Error=20if=20a=20user=20doesn=E2=80=99t?= =?UTF-8?q?=20have=20SSR=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/integrations/web-vitals/src/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/integrations/web-vitals/src/index.ts b/packages/integrations/web-vitals/src/index.ts index d9ec6fbdb28c..2baa0b2e7fb4 100644 --- a/packages/integrations/web-vitals/src/index.ts +++ b/packages/integrations/web-vitals/src/index.ts @@ -17,6 +17,14 @@ export default function webVitals() { ); } + if (config.output !== 'hybrid' && config.output !== 'server') { + throw new AstroError( + 'No SSR adapter found.', + '`@astrojs/web-vitals` requires your site to be built with `hybrid` or `server` output.\n' + + 'Please add an SSR adapter: https://docs.astro.build/en/guides/server-side-rendering/' + ); + } + // Middleware that adds a `` tag to each page. addMiddleware({ entrypoint: '@astrojs/web-vitals/middleware', order: 'post' }); // Endpoint that collects metrics and inserts them in Astro DB. From 7d3be6e14f2ee9ce68a09a98fbad9cf797a84595 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 12:28:59 +0200 Subject: [PATCH 12/29] Simplify package.json `exports` --- packages/integrations/web-vitals/package.json | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/packages/integrations/web-vitals/package.json b/packages/integrations/web-vitals/package.json index ca7ef5665d4a..a3fc5c530b3d 100644 --- a/packages/integrations/web-vitals/package.json +++ b/packages/integrations/web-vitals/package.json @@ -16,26 +16,11 @@ ], "bugs": "https://github.com/withastro/astro/issues", "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js" - }, - "./middleware": { - "types": "./dist/middleware.d.ts", - "import": "./dist/middleware.js" - }, - "./endpoint": { - "types": "./dist/endpoint.d.ts", - "import": "./dist/endpoint.js" - }, - "./client-script": { - "types": "./dist/client-script.d.ts", - "import": "./dist/client-script.js" - }, - "./db-config": { - "types": "./dist/db-config.d.ts", - "import": "./dist/db-config.js" - } + ".": "./dist/index.js", + "./middleware": "./dist/middleware.js", + "./endpoint": "./dist/endpoint.js", + "./client-script": "./dist/client-script.js", + "./db-config": "./dist/db-config.js" }, "files": [ "dist" From 3adc646ae95dfaafa14552595b2e75f3d4d1ad39 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 12:32:44 +0200 Subject: [PATCH 13/29] Initial test files --- .../web-vitals/test/basics.test.js | 44 +++++++++++++++++++ .../web-vitals/test/test-utils.js | 16 +++++++ 2 files changed, 60 insertions(+) create mode 100644 packages/integrations/web-vitals/test/basics.test.js create mode 100644 packages/integrations/web-vitals/test/test-utils.js diff --git a/packages/integrations/web-vitals/test/basics.test.js b/packages/integrations/web-vitals/test/basics.test.js new file mode 100644 index 000000000000..370f2d5f8bf4 --- /dev/null +++ b/packages/integrations/web-vitals/test/basics.test.js @@ -0,0 +1,44 @@ +// @ts-check + +import * as assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import { parseHTML } from 'linkedom'; +import { loadFixture } from './test-utils.js'; + +describe('Web Vitals integration basics', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + /** @type {import('./test-utils').DevServer} */ + let devServer; + + before(async () => { + fixture = await loadFixture({ root: './fixtures/basics/' }); + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('adds a meta tag to the page', async () => { + const html = await fixture.fetch('/').then((res) => res.text()); + const { document } = parseHTML(html); + const meta = document.querySelector('head > meta[name="x-astro-vitals-route"]'); + assert.ok(meta); + assert.equal(meta.getAttribute('content'), '/'); + }); + + it('adds a meta tag using the route pattern to the page', async () => { + const html = await fixture.fetch('/test').then((res) => res.text()); + const { document } = parseHTML(html); + const meta = document.querySelector('head > meta[name="x-astro-vitals-route"]'); + assert.ok(meta); + assert.equal(meta.getAttribute('content'), '/[dynamic]'); + }); + + it.todo('accepts data to the injected endpoint', async () => { + const res = await fixture.fetch('/_/astro-vitals', { method: 'POST', body: '[]' }); + // console.log(res) + assert.equal(res.status, 200); + }) +}); diff --git a/packages/integrations/web-vitals/test/test-utils.js b/packages/integrations/web-vitals/test/test-utils.js new file mode 100644 index 000000000000..8dd4d970bdd5 --- /dev/null +++ b/packages/integrations/web-vitals/test/test-utils.js @@ -0,0 +1,16 @@ +import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js'; + +/** @typedef {import('../../../astro/test/test-utils').Fixture} Fixture */ +/** @typedef {import('../../../astro/test/test-utils').DevServer} DevServer */ + +/** @type {typeof import('../../../astro/test/test-utils.js')['loadFixture']} */ +export function loadFixture(inlineConfig) { + if (!inlineConfig?.root) throw new Error("Must provide { root: './fixtures/...' }"); + + // resolve the relative root (i.e. "./fixtures/tailwindcss") to a full filepath + // without this, the main `loadFixture` helper will resolve relative to `packages/astro/test` + return baseLoadFixture({ + ...inlineConfig, + root: new URL(inlineConfig.root, import.meta.url).toString(), + }); +} From 8ce23d266c205b7775a9466a680619f1a78c6554 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 12:35:53 +0200 Subject: [PATCH 14/29] Peace of mind --- packages/integrations/web-vitals/test/basics.test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/integrations/web-vitals/test/basics.test.js b/packages/integrations/web-vitals/test/basics.test.js index 370f2d5f8bf4..e8109caa3272 100644 --- a/packages/integrations/web-vitals/test/basics.test.js +++ b/packages/integrations/web-vitals/test/basics.test.js @@ -13,7 +13,7 @@ describe('Web Vitals integration basics', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/basics/' }); - devServer = await fixture.startDevServer(); + devServer = await fixture.startDevServer({}); }); after(async () => { @@ -21,7 +21,7 @@ describe('Web Vitals integration basics', () => { }); it('adds a meta tag to the page', async () => { - const html = await fixture.fetch('/').then((res) => res.text()); + const html = await fixture.fetch('/', {}).then((res) => res.text()); const { document } = parseHTML(html); const meta = document.querySelector('head > meta[name="x-astro-vitals-route"]'); assert.ok(meta); @@ -29,7 +29,7 @@ describe('Web Vitals integration basics', () => { }); it('adds a meta tag using the route pattern to the page', async () => { - const html = await fixture.fetch('/test').then((res) => res.text()); + const html = await fixture.fetch('/test', {}).then((res) => res.text()); const { document } = parseHTML(html); const meta = document.querySelector('head > meta[name="x-astro-vitals-route"]'); assert.ok(meta); @@ -38,7 +38,6 @@ describe('Web Vitals integration basics', () => { it.todo('accepts data to the injected endpoint', async () => { const res = await fixture.fetch('/_/astro-vitals', { method: 'POST', body: '[]' }); - // console.log(res) assert.equal(res.status, 200); }) }); From a397c63a06a68fe25be7f7a2326893ea35b4a6ff Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 12:36:02 +0200 Subject: [PATCH 15/29] `pnpm format` --- packages/db/src/utils.ts | 5 ++++- packages/integrations/web-vitals/src/client-script.ts | 2 +- packages/integrations/web-vitals/src/db-config.ts | 2 +- packages/integrations/web-vitals/src/endpoint.ts | 7 +++++-- packages/integrations/web-vitals/src/middleware.ts | 2 +- packages/integrations/web-vitals/test/basics.test.js | 4 ++-- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/db/src/utils.ts b/packages/db/src/utils.ts index 7a98cca35cfc..0f244cd00be5 100644 --- a/packages/db/src/utils.ts +++ b/packages/db/src/utils.ts @@ -7,5 +7,8 @@ export function asDrizzleTable< TableName extends string = string, TColumns extends ColumnsConfig = ColumnsConfig, >(name: TableName, tableConfig: TableConfig) { - return internal_asDrizzleTable(name, tableSchema.parse(tableConfig)) as Table; + return internal_asDrizzleTable(name, tableSchema.parse(tableConfig)) as Table< + TableName, + TColumns + >; } diff --git a/packages/integrations/web-vitals/src/client-script.ts b/packages/integrations/web-vitals/src/client-script.ts index 0d88363695ba..df16a8d4cf9d 100644 --- a/packages/integrations/web-vitals/src/client-script.ts +++ b/packages/integrations/web-vitals/src/client-script.ts @@ -1,4 +1,4 @@ -import { onCLS, onINP, onLCP, onFID, onFCP, onTTFB, type Metric } from 'web-vitals'; +import { type Metric, onCLS, onFCP, onFID, onINP, onLCP, onTTFB } from 'web-vitals'; import type { ClientMetric } from './schemas.js'; const pathname = location.pathname.replace(/(?<=.)\/$/, ''); diff --git a/packages/integrations/web-vitals/src/db-config.ts b/packages/integrations/web-vitals/src/db-config.ts index 8405fb223f56..918850f63580 100644 --- a/packages/integrations/web-vitals/src/db-config.ts +++ b/packages/integrations/web-vitals/src/db-config.ts @@ -1,5 +1,5 @@ -import { asDrizzleTable } from '@astrojs/db/utils'; import { column, defineDb, defineTable } from 'astro:db'; +import { asDrizzleTable } from '@astrojs/db/utils'; const Metric = defineTable({ columns: { diff --git a/packages/integrations/web-vitals/src/endpoint.ts b/packages/integrations/web-vitals/src/endpoint.ts index bca98ce04088..10dea1ca88aa 100644 --- a/packages/integrations/web-vitals/src/endpoint.ts +++ b/packages/integrations/web-vitals/src/endpoint.ts @@ -1,7 +1,7 @@ +import { db, sql } from 'astro:db'; import type { APIRoute } from 'astro'; import { AstrojsWebVitals_Metric } from './db-config.js'; import { ServerMetricSchema } from './schemas.js'; -import { db, sql } from 'astro:db'; export const prerender = false; @@ -12,7 +12,10 @@ export const ALL: APIRoute = async ({ request }) => { await db .insert(AstrojsWebVitals_Metric) .values(body) - .onConflictDoUpdate({ target: AstrojsWebVitals_Metric.id, set: { value: sql`excluded.value` } }); + .onConflictDoUpdate({ + target: AstrojsWebVitals_Metric.id, + set: { value: sql`excluded.value` }, + }); } catch (error) { console.error(error); } diff --git a/packages/integrations/web-vitals/src/middleware.ts b/packages/integrations/web-vitals/src/middleware.ts index 94c9b1d3e569..b4994c902c1c 100644 --- a/packages/integrations/web-vitals/src/middleware.ts +++ b/packages/integrations/web-vitals/src/middleware.ts @@ -1,4 +1,4 @@ -import type { MiddlewareHandler } from "astro"; +import type { MiddlewareHandler } from 'astro'; /** * Middleware which adds the web vitals `` tag to each page’s ``. diff --git a/packages/integrations/web-vitals/test/basics.test.js b/packages/integrations/web-vitals/test/basics.test.js index e8109caa3272..a5c0341dafd9 100644 --- a/packages/integrations/web-vitals/test/basics.test.js +++ b/packages/integrations/web-vitals/test/basics.test.js @@ -27,7 +27,7 @@ describe('Web Vitals integration basics', () => { assert.ok(meta); assert.equal(meta.getAttribute('content'), '/'); }); - + it('adds a meta tag using the route pattern to the page', async () => { const html = await fixture.fetch('/test', {}).then((res) => res.text()); const { document } = parseHTML(html); @@ -39,5 +39,5 @@ describe('Web Vitals integration basics', () => { it.todo('accepts data to the injected endpoint', async () => { const res = await fixture.fetch('/_/astro-vitals', { method: 'POST', body: '[]' }); assert.equal(res.status, 200); - }) + }); }); From dceeb1a7398a609a1aefddec1598edae90b79502 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 13:17:34 +0200 Subject: [PATCH 16/29] Mock `console.error` in tests to track endpoint issues --- .../web-vitals/test/basics.test.js | 69 ++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/packages/integrations/web-vitals/test/basics.test.js b/packages/integrations/web-vitals/test/basics.test.js index a5c0341dafd9..dadb336d1c0b 100644 --- a/packages/integrations/web-vitals/test/basics.test.js +++ b/packages/integrations/web-vitals/test/basics.test.js @@ -1,25 +1,61 @@ // @ts-check import * as assert from 'node:assert/strict'; -import { after, before, describe, it } from 'node:test'; +import { after, before, beforeEach, describe, it } from 'node:test'; import { parseHTML } from 'linkedom'; import { loadFixture } from './test-utils.js'; +/** + * @template {Record void>} T + * @template {keyof T} K + */ +class MockFunction { + /** @type {Parameters[]} */ + calls = []; + + /** + * @param {T} object + * @param {K} property + */ + constructor(object, property) { + this.object = object; + this.property = property; + this.original = object[property]; + object[property] = /** @param {Parameters} args */ (...args) => { + this.calls.push(args); + }; + } + restore() { + this.object[this.property] = this.original; + } + reset() { + this.calls = []; + } +} + describe('Web Vitals integration basics', () => { /** @type {import('./test-utils').Fixture} */ let fixture; /** @type {import('./test-utils').DevServer} */ let devServer; + /** @type {MockFunction} */ + let consoleErrorMock; before(async () => { + consoleErrorMock = new MockFunction(console, 'error'); fixture = await loadFixture({ root: './fixtures/basics/' }); devServer = await fixture.startDevServer({}); }); after(async () => { + consoleErrorMock.restore(); await devServer.stop(); }); + beforeEach(() => { + consoleErrorMock.reset(); + }); + it('adds a meta tag to the page', async () => { const html = await fixture.fetch('/', {}).then((res) => res.text()); const { document } = parseHTML(html); @@ -36,8 +72,35 @@ describe('Web Vitals integration basics', () => { assert.equal(meta.getAttribute('content'), '/[dynamic]'); }); - it.todo('accepts data to the injected endpoint', async () => { - const res = await fixture.fetch('/_/astro-vitals', { method: 'POST', body: '[]' }); + it('returns a 200 response even when bad data is sent to the injected endpoint', async () => { + { + // bad data + const res = await fixture.fetch('/_/astro-vitals', { method: 'POST', body: 'garbage' }); + assert.equal(res.status, 200); + } + { + // no data + const res = await fixture.fetch('/_/astro-vitals', { method: 'POST', body: '[]' }); + assert.equal(res.status, 200); + } + assert.equal(consoleErrorMock.calls.length, 2); + }); + + it('inserts data via the injected endpoint', async () => { + const res = await fixture.fetch('/_/astro-vitals', { + method: 'POST', + body: JSON.stringify([ + { + pathname: '/', + route: '/', + name: 'CLS', + id: 'v3-1711484350895-3748043125387', + value: 0, + rating: 'good', + }, + ]), + }); assert.equal(res.status, 200); + assert.equal(consoleErrorMock.calls.length, 0); }); }); From ef939011f46c4def50c30744f159f8966c12c689 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 13:19:37 +0200 Subject: [PATCH 17/29] Surface error message in test --- packages/integrations/web-vitals/test/basics.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/integrations/web-vitals/test/basics.test.js b/packages/integrations/web-vitals/test/basics.test.js index dadb336d1c0b..b935d687a7ed 100644 --- a/packages/integrations/web-vitals/test/basics.test.js +++ b/packages/integrations/web-vitals/test/basics.test.js @@ -101,6 +101,10 @@ describe('Web Vitals integration basics', () => { ]), }); assert.equal(res.status, 200); - assert.equal(consoleErrorMock.calls.length, 0); + assert.equal( + consoleErrorMock.calls.length, + 0, + 'Endpoint logged errors:\n' + consoleErrorMock.calls[0].join(' ') + ); }); }); From 80c8e067306881ffe192718ea57961ed1795659e Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 16:33:35 +0200 Subject: [PATCH 18/29] Fix lockfile --- pnpm-lock.yaml | 60 +++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7383b61e866..cf008aeebdf6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,8 +22,8 @@ importers: specifier: ^0.5.10 version: 0.5.10(prettier-plugin-astro@0.13.0)(prettier@3.2.5)(typescript@5.4.5) '@biomejs/biome': - specifier: 1.6.4 - version: 1.6.4 + specifier: 1.7.1 + version: 1.7.1 '@changesets/changelog-github': specifier: ^0.5.0 version: 0.5.0 @@ -6080,24 +6080,24 @@ packages: '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - /@biomejs/biome@1.6.4: - resolution: {integrity: sha512-3groVd2oWsLC0ZU+XXgHSNbq31lUcOCBkCcA7sAQGBopHcmL+jmmdoWlY3S61zIh+f2mqQTQte1g6PZKb3JJjA==} + /@biomejs/biome@1.7.1: + resolution: {integrity: sha512-wb2UNoFXcgaMdKXKT5ytsYntaogl2FSTjDt20CZynF3v7OXQUcIpTrr+be3XoOGpoZRj3Ytq9TSpmplUREXmeA==} engines: {node: '>=14.21.3'} hasBin: true requiresBuild: true optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.6.4 - '@biomejs/cli-darwin-x64': 1.6.4 - '@biomejs/cli-linux-arm64': 1.6.4 - '@biomejs/cli-linux-arm64-musl': 1.6.4 - '@biomejs/cli-linux-x64': 1.6.4 - '@biomejs/cli-linux-x64-musl': 1.6.4 - '@biomejs/cli-win32-arm64': 1.6.4 - '@biomejs/cli-win32-x64': 1.6.4 - dev: true - - /@biomejs/cli-darwin-arm64@1.6.4: - resolution: {integrity: sha512-2WZef8byI9NRzGajGj5RTrroW9BxtfbP9etigW1QGAtwu/6+cLkdPOWRAs7uFtaxBNiKFYA8j/BxV5zeAo5QOQ==} + '@biomejs/cli-darwin-arm64': 1.7.1 + '@biomejs/cli-darwin-x64': 1.7.1 + '@biomejs/cli-linux-arm64': 1.7.1 + '@biomejs/cli-linux-arm64-musl': 1.7.1 + '@biomejs/cli-linux-x64': 1.7.1 + '@biomejs/cli-linux-x64-musl': 1.7.1 + '@biomejs/cli-win32-arm64': 1.7.1 + '@biomejs/cli-win32-x64': 1.7.1 + dev: true + + /@biomejs/cli-darwin-arm64@1.7.1: + resolution: {integrity: sha512-qfLrIIB58dkgiY/1tgG6fSCBK22PZaSIf6blweZBsG6iMij05mEuJt50ne+zPnNFNUmt8t43NC/qOXT3iFHQBA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] @@ -6105,8 +6105,8 @@ packages: dev: true optional: true - /@biomejs/cli-darwin-x64@1.6.4: - resolution: {integrity: sha512-uo1zgM7jvzcoDpF6dbGizejDLCqNpUIRkCj/oEK0PB0NUw8re/cn1EnxuOLZqDpn+8G75COLQTOx8UQIBBN/Kg==} + /@biomejs/cli-darwin-x64@1.7.1: + resolution: {integrity: sha512-OGeyNsEcp5VnKbF9/TBjPCTHNEOm7oHegEve07U3KZmzqfpw2Oe3i9DVW8t6vvj1TYbrwWYCld25H34kBDY7Vg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] @@ -6114,8 +6114,8 @@ packages: dev: true optional: true - /@biomejs/cli-linux-arm64-musl@1.6.4: - resolution: {integrity: sha512-Hp8Jwt6rjj0wCcYAEN6/cfwrrPLLlGOXZ56Lei4Pt4jy39+UuPeAVFPeclrrCfxyL1wQ2xPrhd/saTHSL6DoJg==} + /@biomejs/cli-linux-arm64-musl@1.7.1: + resolution: {integrity: sha512-giH0/CzLOJ+wbxLxd5Shnr5xQf5fGnTRWLDe3lzjaF7IplVydNCEeZJtncB01SvyA6DAFJsvQ4LNxzAOQfEVCg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] @@ -6123,8 +6123,8 @@ packages: dev: true optional: true - /@biomejs/cli-linux-arm64@1.6.4: - resolution: {integrity: sha512-wAOieaMNIpLrxGc2/xNvM//CIZg7ueWy3V5A4T7gDZ3OL/Go27EKE59a+vMKsBCYmTt7jFl4yHz0TUkUbodA/w==} + /@biomejs/cli-linux-arm64@1.7.1: + resolution: {integrity: sha512-MQDf5wErj1iBvlcxCyOa0XqZYN8WJrupVgbNnqhntO3yVATg8GxduVUn1fDSaolznkDRsj7Pz3Xu1esBFwvfmg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] @@ -6132,8 +6132,8 @@ packages: dev: true optional: true - /@biomejs/cli-linux-x64-musl@1.6.4: - resolution: {integrity: sha512-wqi0hr8KAx5kBO0B+m5u8QqiYFFBJOSJVSuRqTeGWW+GYLVUtXNidykNqf1JsW6jJDpbkSp2xHKE/bTlVaG2Kg==} + /@biomejs/cli-linux-x64-musl@1.7.1: + resolution: {integrity: sha512-ySNDtPhsLxU125IFHHAxfpoHBpkM56s4mEXeO70GZtgZay/o1h8IUPWCWf5Z7gKgc4jwgYN1U1U9xabI3hZVAg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] @@ -6141,8 +6141,8 @@ packages: dev: true optional: true - /@biomejs/cli-linux-x64@1.6.4: - resolution: {integrity: sha512-qTWhuIw+/ePvOkjE9Zxf5OqSCYxtAvcTJtVmZT8YQnmY2I62JKNV2m7tf6O5ViKZUOP0mOQ6NgqHKcHH1eT8jw==} + /@biomejs/cli-linux-x64@1.7.1: + resolution: {integrity: sha512-3wmCsGcC3KZ4pfTknXHfyMMlXPMhgfXVAcG5GlrR+Tq2JGiAw0EUydaLpsSBEbcG7IxH6OiUZEJZ95kAycCHBA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] @@ -6150,8 +6150,8 @@ packages: dev: true optional: true - /@biomejs/cli-win32-arm64@1.6.4: - resolution: {integrity: sha512-Wp3FiEeF6v6C5qMfLkHwf4YsoNHr/n0efvoC8jCKO/kX05OXaVExj+1uVQ1eGT7Pvx0XVm/TLprRO0vq/V6UzA==} + /@biomejs/cli-win32-arm64@1.7.1: + resolution: {integrity: sha512-8hIDakEqZn0i6+388noYKdZ0ZrovTwnvMU/Qp/oJou0G7EPVdXupOe0oxiQSdRN0W7f6CS/yjPCYuVGzDG6r0g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] @@ -6159,8 +6159,8 @@ packages: dev: true optional: true - /@biomejs/cli-win32-x64@1.6.4: - resolution: {integrity: sha512-mz183Di5hTSGP7KjNWEhivcP1wnHLGmOxEROvoFsIxMYtDhzJDad4k5gI/1JbmA0xe4n52vsgqo09tBhrMT/Zg==} + /@biomejs/cli-win32-x64@1.7.1: + resolution: {integrity: sha512-3W9k3uH6Ea6VOpAS9xkkAlS0LTfnGQjmIUCegZ8SDtK2NgJ1gO+qdEkGJb0ltahusFTN1QxJ107dM7ASA9IUEg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] From 3ec42de5a2a0e1689ceb89fe140d3f10240983f9 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 21:41:21 +0200 Subject: [PATCH 19/29] Recreate tables when first establishing db client --- packages/db/src/core/integration/vite-plugin-db.ts | 7 +++++-- packages/db/src/runtime/index.ts | 2 +- packages/db/src/runtime/seed-local.ts | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/db/src/core/integration/vite-plugin-db.ts b/packages/db/src/core/integration/vite-plugin-db.ts index df31d527673b..0dd874255998 100644 --- a/packages/db/src/core/integration/vite-plugin-db.ts +++ b/packages/db/src/core/integration/vite-plugin-db.ts @@ -119,18 +119,21 @@ export function getLocalVirtualModContents({ const dbUrl = new URL(DB_PATH, root); return ` -import { asDrizzleTable, createLocalDatabaseClient, normalizeDatabaseUrl } from ${RUNTIME_IMPORT}; +import { asDrizzleTable, createLocalDatabaseClient, normalizeDatabaseUrl, recreateTables } from ${RUNTIME_IMPORT}; ${shouldSeed ? `import { seedLocal } from ${RUNTIME_IMPORT};` : ''} ${shouldSeed ? integrationSeedImportStatements.join('\n') : ''} +const tables = ${JSON.stringify(tables)}; + const dbUrl = normalizeDatabaseUrl(import.meta.env.ASTRO_DATABASE_FILE, ${JSON.stringify(dbUrl)}); export const db = createLocalDatabaseClient({ dbUrl }); +await recreateTables({ db, tables }); ${ shouldSeed ? `await seedLocal({ db, - tables: ${JSON.stringify(tables)}, + tables, userSeedGlob: import.meta.glob(${JSON.stringify(userSeedFilePaths)}, { eager: true }), integrationSeedFunctions: [${integrationSeedImportNames.join(',')}], });` diff --git a/packages/db/src/runtime/index.ts b/packages/db/src/runtime/index.ts index 06d08a8793f9..7055b8c243bb 100644 --- a/packages/db/src/runtime/index.ts +++ b/packages/db/src/runtime/index.ts @@ -14,7 +14,7 @@ import { pathToFileURL } from './utils.js'; export type { Table } from './types.js'; export { createRemoteDatabaseClient, createLocalDatabaseClient } from './db-client.js'; -export { seedLocal } from './seed-local.js'; +export { seedLocal, recreateTables } from './seed-local.js'; export function hasPrimaryKey(column: DBColumn) { return 'primaryKey' in column.schema && !!column.schema.primaryKey; diff --git a/packages/db/src/runtime/seed-local.ts b/packages/db/src/runtime/seed-local.ts index 3fb6c61d0c6b..cb2208cabdbb 100644 --- a/packages/db/src/runtime/seed-local.ts +++ b/packages/db/src/runtime/seed-local.ts @@ -44,7 +44,7 @@ export async function seedLocal({ } } -async function recreateTables({ db, tables }: { db: LibSQLDatabase; tables: DBTables }) { +export async function recreateTables({ db, tables }: { db: LibSQLDatabase; tables: DBTables }) { const setupQueries: SQL[] = []; for (const [name, table] of Object.entries(tables)) { const dropQuery = sql.raw(`DROP TABLE IF EXISTS ${sqlite.escapeName(name)}`); From 5bf5dd496bf55c0713754f2a4f7726693b759744 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 26 Apr 2024 21:41:41 +0200 Subject: [PATCH 20/29] Another test --- packages/integrations/web-vitals/test/basics.test.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/integrations/web-vitals/test/basics.test.js b/packages/integrations/web-vitals/test/basics.test.js index b935d687a7ed..75d7ca0143ac 100644 --- a/packages/integrations/web-vitals/test/basics.test.js +++ b/packages/integrations/web-vitals/test/basics.test.js @@ -86,6 +86,14 @@ describe('Web Vitals integration basics', () => { assert.equal(consoleErrorMock.calls.length, 2); }); + it('validates data sent to the injected endpoint with Zod', async () => { + const res = await fixture.fetch('/_/astro-vitals', { method: 'POST', body: '[{}]' }); + assert.equal(res.status, 200); + const call = consoleErrorMock.calls[0][0]; + assert.ok(call instanceof Error); + assert.equal(call.name, 'ZodError'); + }); + it('inserts data via the injected endpoint', async () => { const res = await fixture.fetch('/_/astro-vitals', { method: 'POST', @@ -104,7 +112,7 @@ describe('Web Vitals integration basics', () => { assert.equal( consoleErrorMock.calls.length, 0, - 'Endpoint logged errors:\n' + consoleErrorMock.calls[0].join(' ') + 'Endpoint logged errors:\n' + consoleErrorMock.calls[0]?.join(' ') ); }); }); From 9b7092e56a580c6e8ee06481698da653c1c28e1c Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Mon, 29 Apr 2024 19:23:42 +0200 Subject: [PATCH 21/29] Flesh out README --- packages/integrations/web-vitals/README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/integrations/web-vitals/README.md b/packages/integrations/web-vitals/README.md index 644b3b19d218..e3fddbea8316 100644 --- a/packages/integrations/web-vitals/README.md +++ b/packages/integrations/web-vitals/README.md @@ -1,10 +1,23 @@ -# @astrojs/web-vitals ⏱️ +# @astrojs/web-vitals (experimental) ⏱️ This **[Astro integration][astro-integration]** enables tracking real-world website performance and storing the data in [Astro DB][db]. - +- [Astro DB](https://astro.build/db) — `@astrojs/web-vitals` will store performance data in Astro DB in production +- [An SSR adapter](https://docs.astro.build/en/guides/server-side-rendering/) — `@astrojs/web-vitals` injects a server endpoint to manage saving data to Astro DB + +## Installation + +1. Install and configure the Web Vitals integration using `astro add`: + + ```sh + npx astro add web-vitals + ``` + +2. Redeploy your site. + +3. Visit your project dashboard at https://studio.astro.build to see the data collected. ## Support @@ -30,7 +43,6 @@ Copyright (c) 2023–present [Astro][astro] [astro]: https://astro.build/ [db]: https://astro.build/db/ -[docs]: https://docs.astro.build/en/guides/integrations-guide/web-vitals/ [contributing]: https://github.com/withastro/astro/blob/main/CONTRIBUTING.md [coc]: https://github.com/withastro/.github/blob/main/CODE_OF_CONDUCT.md [community]: https://github.com/withastro/.github/blob/main/COMMUNITY_GUIDE.md From 201e6e4aa2000243239315ae201044e546f7b1ee Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Mon, 29 Apr 2024 19:25:15 +0200 Subject: [PATCH 22/29] Add changesets --- .changeset/great-swans-punch.md | 5 +++++ .changeset/tough-plants-kiss.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/great-swans-punch.md create mode 100644 .changeset/tough-plants-kiss.md diff --git a/.changeset/great-swans-punch.md b/.changeset/great-swans-punch.md new file mode 100644 index 000000000000..60725a224ce0 --- /dev/null +++ b/.changeset/great-swans-punch.md @@ -0,0 +1,5 @@ +--- +"@astrojs/web-vitals": minor +--- + +Adds a new web-vitals integration powered by Astro DB diff --git a/.changeset/tough-plants-kiss.md b/.changeset/tough-plants-kiss.md new file mode 100644 index 000000000000..be96a7406f81 --- /dev/null +++ b/.changeset/tough-plants-kiss.md @@ -0,0 +1,5 @@ +--- +"@astrojs/db": patch +--- + +Fixes database table creation for Astro DB integrations From d575a71e1333321c701d1d37bfaa8ffcbbdf591f Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Tue, 30 Apr 2024 16:26:48 +0200 Subject: [PATCH 23/29] Improve README --- packages/integrations/web-vitals/README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/integrations/web-vitals/README.md b/packages/integrations/web-vitals/README.md index e3fddbea8316..3a4c5fb0141d 100644 --- a/packages/integrations/web-vitals/README.md +++ b/packages/integrations/web-vitals/README.md @@ -15,9 +15,17 @@ This **[Astro integration][astro-integration]** enables tracking real-world webs npx astro add web-vitals ``` -2. Redeploy your site. +2. Push the tables added by the Web Vitals integration to Astro Studio: -3. Visit your project dashboard at https://studio.astro.build to see the data collected. + ```sh + npx astro db push + ``` + +3. Redeploy your site. + +4. Visit your project dashboard at https://studio.astro.build to see the data collected. + +Learn more about [Astro DB](https://docs.astro.build/en/guides/astro-db/) and [deploying with Astro Studio](https://docs.astro.build/en/guides/astro-db/#astro-studio) in the Astro docs. ## Support From b54b58223e62eb6b68aa8e197a829702d3aca381 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 3 May 2024 16:22:32 +0200 Subject: [PATCH 24/29] Rename endpoint path --- packages/integrations/web-vitals/src/client-script.ts | 6 +++--- packages/integrations/web-vitals/src/constants.ts | 1 + packages/integrations/web-vitals/src/index.ts | 3 ++- packages/integrations/web-vitals/test/basics.test.js | 8 ++++---- 4 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 packages/integrations/web-vitals/src/constants.ts diff --git a/packages/integrations/web-vitals/src/client-script.ts b/packages/integrations/web-vitals/src/client-script.ts index df16a8d4cf9d..b69fa6772eb9 100644 --- a/packages/integrations/web-vitals/src/client-script.ts +++ b/packages/integrations/web-vitals/src/client-script.ts @@ -1,4 +1,5 @@ import { type Metric, onCLS, onFCP, onFID, onINP, onLCP, onTTFB } from 'web-vitals'; +import { WEB_VITALS_ENDPOINT_PATH } from './constants.js'; import type { ClientMetric } from './schemas.js'; const pathname = location.pathname.replace(/(?<=.)\/$/, ''); @@ -20,9 +21,8 @@ function flushQueue() { rating, })); const body = JSON.stringify(rawBody); - const endpoint = '/_/astro-vitals'; - if (navigator.sendBeacon) navigator.sendBeacon(endpoint, body); - else fetch(endpoint, { body, method: 'POST', keepalive: true }); + if (navigator.sendBeacon) navigator.sendBeacon(WEB_VITALS_ENDPOINT_PATH, body); + else fetch(WEB_VITALS_ENDPOINT_PATH, { body, method: 'POST', keepalive: true }); queue.clear(); } diff --git a/packages/integrations/web-vitals/src/constants.ts b/packages/integrations/web-vitals/src/constants.ts new file mode 100644 index 000000000000..7df5bb8b6572 --- /dev/null +++ b/packages/integrations/web-vitals/src/constants.ts @@ -0,0 +1 @@ +export const WEB_VITALS_ENDPOINT_PATH = '/_web-vitals' diff --git a/packages/integrations/web-vitals/src/index.ts b/packages/integrations/web-vitals/src/index.ts index 2baa0b2e7fb4..f8a5ad433496 100644 --- a/packages/integrations/web-vitals/src/index.ts +++ b/packages/integrations/web-vitals/src/index.ts @@ -1,5 +1,6 @@ import { defineDbIntegration } from '@astrojs/db/utils'; import { AstroError } from 'astro/errors'; +import { WEB_VITALS_ENDPOINT_PATH } from './constants.js'; export default function webVitals() { return defineDbIntegration({ @@ -30,7 +31,7 @@ export default function webVitals() { // Endpoint that collects metrics and inserts them in Astro DB. injectRoute({ entrypoint: '@astrojs/web-vitals/endpoint', - pattern: '/_/astro-vitals', + pattern: WEB_VITALS_ENDPOINT_PATH, prerender: false, }); // Client-side performance measurement script. diff --git a/packages/integrations/web-vitals/test/basics.test.js b/packages/integrations/web-vitals/test/basics.test.js index 75d7ca0143ac..937619b483d4 100644 --- a/packages/integrations/web-vitals/test/basics.test.js +++ b/packages/integrations/web-vitals/test/basics.test.js @@ -75,19 +75,19 @@ describe('Web Vitals integration basics', () => { it('returns a 200 response even when bad data is sent to the injected endpoint', async () => { { // bad data - const res = await fixture.fetch('/_/astro-vitals', { method: 'POST', body: 'garbage' }); + const res = await fixture.fetch('/_web-vitals', { method: 'POST', body: 'garbage' }); assert.equal(res.status, 200); } { // no data - const res = await fixture.fetch('/_/astro-vitals', { method: 'POST', body: '[]' }); + const res = await fixture.fetch('/_web-vitals', { method: 'POST', body: '[]' }); assert.equal(res.status, 200); } assert.equal(consoleErrorMock.calls.length, 2); }); it('validates data sent to the injected endpoint with Zod', async () => { - const res = await fixture.fetch('/_/astro-vitals', { method: 'POST', body: '[{}]' }); + const res = await fixture.fetch('/_web-vitals', { method: 'POST', body: '[{}]' }); assert.equal(res.status, 200); const call = consoleErrorMock.calls[0][0]; assert.ok(call instanceof Error); @@ -95,7 +95,7 @@ describe('Web Vitals integration basics', () => { }); it('inserts data via the injected endpoint', async () => { - const res = await fixture.fetch('/_/astro-vitals', { + const res = await fixture.fetch('/_web-vitals', { method: 'POST', body: JSON.stringify([ { From ce1838cb7c6ed1f3e8e9d5c3e4b5d4184e80e9d5 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 3 May 2024 17:02:11 +0200 Subject: [PATCH 25/29] Delete .changeset/tough-plants-kiss.md --- .changeset/tough-plants-kiss.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/tough-plants-kiss.md diff --git a/.changeset/tough-plants-kiss.md b/.changeset/tough-plants-kiss.md deleted file mode 100644 index be96a7406f81..000000000000 --- a/.changeset/tough-plants-kiss.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@astrojs/db": patch ---- - -Fixes database table creation for Astro DB integrations From 9bb97d83750a6b2e5b9527f2e0b2175dc3db48be Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 3 May 2024 17:13:50 +0200 Subject: [PATCH 26/29] Discard changes to packages/db/src/core/integration/vite-plugin-db.ts --- packages/db/src/core/integration/vite-plugin-db.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/db/src/core/integration/vite-plugin-db.ts b/packages/db/src/core/integration/vite-plugin-db.ts index 0dd874255998..df31d527673b 100644 --- a/packages/db/src/core/integration/vite-plugin-db.ts +++ b/packages/db/src/core/integration/vite-plugin-db.ts @@ -119,21 +119,18 @@ export function getLocalVirtualModContents({ const dbUrl = new URL(DB_PATH, root); return ` -import { asDrizzleTable, createLocalDatabaseClient, normalizeDatabaseUrl, recreateTables } from ${RUNTIME_IMPORT}; +import { asDrizzleTable, createLocalDatabaseClient, normalizeDatabaseUrl } from ${RUNTIME_IMPORT}; ${shouldSeed ? `import { seedLocal } from ${RUNTIME_IMPORT};` : ''} ${shouldSeed ? integrationSeedImportStatements.join('\n') : ''} -const tables = ${JSON.stringify(tables)}; - const dbUrl = normalizeDatabaseUrl(import.meta.env.ASTRO_DATABASE_FILE, ${JSON.stringify(dbUrl)}); export const db = createLocalDatabaseClient({ dbUrl }); -await recreateTables({ db, tables }); ${ shouldSeed ? `await seedLocal({ db, - tables, + tables: ${JSON.stringify(tables)}, userSeedGlob: import.meta.glob(${JSON.stringify(userSeedFilePaths)}, { eager: true }), integrationSeedFunctions: [${integrationSeedImportNames.join(',')}], });` From 5dc6a15b92fd5eda75b734a61a96d9df3d29d8db Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 3 May 2024 17:13:55 +0200 Subject: [PATCH 27/29] Discard changes to packages/db/src/runtime/index.ts --- packages/db/src/runtime/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/db/src/runtime/index.ts b/packages/db/src/runtime/index.ts index f9ee4185a3b5..544df372a503 100644 --- a/packages/db/src/runtime/index.ts +++ b/packages/db/src/runtime/index.ts @@ -14,7 +14,7 @@ import { pathToFileURL } from './utils.js'; export type { Table } from './types.js'; export { createRemoteDatabaseClient, createLocalDatabaseClient } from './db-client.js'; -export { seedLocal, recreateTables } from './seed-local.js'; +export { seedLocal } from './seed-local.js'; export function hasPrimaryKey(column: DBColumn) { return 'primaryKey' in column.schema && !!column.schema.primaryKey; From f494238aa35ac9fe623bf379f26763cb92899985 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 3 May 2024 17:14:02 +0200 Subject: [PATCH 28/29] Discard changes to packages/db/src/runtime/seed-local.ts --- packages/db/src/runtime/seed-local.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/db/src/runtime/seed-local.ts b/packages/db/src/runtime/seed-local.ts index cb2208cabdbb..3fb6c61d0c6b 100644 --- a/packages/db/src/runtime/seed-local.ts +++ b/packages/db/src/runtime/seed-local.ts @@ -44,7 +44,7 @@ export async function seedLocal({ } } -export async function recreateTables({ db, tables }: { db: LibSQLDatabase; tables: DBTables }) { +async function recreateTables({ db, tables }: { db: LibSQLDatabase; tables: DBTables }) { const setupQueries: SQL[] = []; for (const [name, table] of Object.entries(tables)) { const dropQuery = sql.raw(`DROP TABLE IF EXISTS ${sqlite.escapeName(name)}`); From 23bbe95a3c5020c885cc1c348c534bad56a4f168 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Fri, 3 May 2024 17:28:00 +0200 Subject: [PATCH 29/29] Bump peer dep version of `@astrojs/db` --- packages/integrations/web-vitals/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integrations/web-vitals/package.json b/packages/integrations/web-vitals/package.json index a3fc5c530b3d..1719f423ee0f 100644 --- a/packages/integrations/web-vitals/package.json +++ b/packages/integrations/web-vitals/package.json @@ -35,7 +35,7 @@ "web-vitals": "^3.5.2" }, "peerDependencies": { - "@astrojs/db": "^0.10.6" + "@astrojs/db": "^0.11.0" }, "devDependencies": { "@astrojs/db": "workspace:*",