Migrate from @vercel/postgres to pg

This commit is contained in:
Sam Becker 2024-04-30 17:15:50 -05:00
parent aec3963f17
commit 74ec5fd234
11 changed files with 64 additions and 63 deletions

View File

@ -28,7 +28,6 @@
"@vercel/analytics": "^1.2.2",
"@vercel/blob": "^0.23.2",
"@vercel/kv": "^1.0.1",
"@vercel/postgres": "^0.8.0",
"@vercel/speed-insights": "^1.0.10",
"ai": "^3.0.34",
"autoprefixer": "10.4.19",

52
pnpm-lock.yaml generated
View File

@ -65,9 +65,6 @@ importers:
'@vercel/kv':
specifier: ^1.0.1
version: 1.0.1
'@vercel/postgres':
specifier: ^0.8.0
version: 0.8.0
'@vercel/speed-insights':
specifier: ^1.0.10
version: 1.0.10(next@14.2.3(@babel/core@7.24.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(svelte@4.2.15)(vue@3.4.25(typescript@5.4.5))
@ -690,9 +687,6 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
'@neondatabase/serverless@0.7.2':
resolution: {integrity: sha512-wU3WA2uTyNO7wjPs3Mg0G01jztAxUxzd9/mskMmtPwPTjf7JKWi9AW5/puOGXLxmZ9PVgRFeBVRVYq5nBPhsCg==}
'@next/bundle-analyzer@14.2.3':
resolution: {integrity: sha512-Z88hbbngMs7njZKI8kTJIlpdLKYfMSLwnsqYe54AP4aLmgL70/Ynx/J201DQ+q2Lr6FxFw1uCeLGImDrHOl2ZA==}
@ -1363,9 +1357,6 @@ packages:
'@types/pg@8.11.5':
resolution: {integrity: sha512-2xMjVviMxneZHDHX5p5S6tsRRs7TpDHeeK7kTTMe/kAC/mRRNjWHjZg0rkiY+e17jXSZV3zJYDxXV8Cy72/Vuw==}
'@types/pg@8.6.6':
resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==}
'@types/prop-types@15.7.12':
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
@ -1514,10 +1505,6 @@ packages:
resolution: {integrity: sha512-uTKddsqVYS2GRAM/QMNNXCTuw9N742mLoGRXoNDcyECaxEXvIHG0dEY+ZnYISV4Vz534VwJO+64fd9XeSggSKw==}
engines: {node: '>=14.6'}
'@vercel/postgres@0.8.0':
resolution: {integrity: sha512-/QUV9ExwaNdKooRjOQqvrKNVnRvsaXeukPNI5DB1ovUTesglfR/fparw7ngo1KUWWKIVpEj2TRrA+ObRHRdaLg==}
engines: {node: '>=14.6'}
'@vercel/speed-insights@1.0.10':
resolution: {integrity: sha512-4uzdKB0RW6Ff2FkzshzjZ+RlJfLPxgm/00i0XXgxfMPhwnnsk92YgtqsxT9OcPLdJUyVU1DqFlSWWjIQMPkh0g==}
peerDependencies:
@ -4174,18 +4161,6 @@ packages:
utf-8-validate:
optional: true
ws@8.14.2:
resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
ws@8.16.0:
resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
engines: {node: '>=10.0.0'}
@ -5257,10 +5232,6 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.4.15
'@neondatabase/serverless@0.7.2':
dependencies:
'@types/pg': 8.6.6
'@next/bundle-analyzer@14.2.3(bufferutil@4.0.8)(utf-8-validate@6.0.3)':
dependencies:
webpack-bundle-analyzer: 4.10.1(bufferutil@4.0.8)(utf-8-validate@6.0.3)
@ -6056,12 +6027,6 @@ snapshots:
pg-protocol: 1.6.1
pg-types: 4.0.2
'@types/pg@8.6.6':
dependencies:
'@types/node': 20.12.7
pg-protocol: 1.6.1
pg-types: 2.2.0
'@types/prop-types@15.7.12': {}
'@types/react-dom@18.3.0':
@ -6247,13 +6212,6 @@ snapshots:
dependencies:
'@upstash/redis': 1.25.1
'@vercel/postgres@0.8.0':
dependencies:
'@neondatabase/serverless': 0.7.2
bufferutil: 4.0.8
utf-8-validate: 6.0.3
ws: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3)
'@vercel/speed-insights@1.0.10(next@14.2.3(@babel/core@7.24.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(svelte@4.2.15)(vue@3.4.25(typescript@5.4.5))':
optionalDependencies:
next: 14.2.3(@babel/core@7.24.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -6608,6 +6566,7 @@ snapshots:
bufferutil@4.0.8:
dependencies:
node-gyp-build: 4.8.0
optional: true
busboy@1.6.0:
dependencies:
@ -8266,7 +8225,8 @@ snapshots:
dependencies:
whatwg-url: 5.0.0
node-gyp-build@4.8.0: {}
node-gyp-build@4.8.0:
optional: true
node-int64@0.4.0: {}
@ -9169,6 +9129,7 @@ snapshots:
utf-8-validate@6.0.3:
dependencies:
node-gyp-build: 4.8.0
optional: true
util-deprecate@1.0.2: {}
@ -9309,11 +9270,6 @@ snapshots:
bufferutil: 4.0.8
utf-8-validate: 6.0.3
ws@8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3):
optionalDependencies:
bufferutil: 4.0.8
utf-8-validate: 6.0.3
ws@8.16.0(bufferutil@4.0.8)(utf-8-validate@6.0.3):
optionalDependencies:
bufferutil: 4.0.8

View File

@ -5,7 +5,7 @@ import { getPhotosCountIncludingHiddenCached } from '@/photo/cache';
import StorageUrls from '@/admin/StorageUrls';
import { PRO_MODE_ENABLED } from '@/site/config';
import { getStoragePhotoUrlsNoStore } from '@/services/storage/cache';
import { getPhotos } from '@/services/vercel-postgres';
import { getPhotos } from '@/photo/db';
import { revalidatePath } from 'next/cache';
import AdminPhotoTable from '@/admin/AdminPhotoTable';
import AdminPhotoTableInfinite from

View File

@ -4,7 +4,7 @@ import { getPhotosCached } from '@/photo/cache';
import TagForm from '@/tag/TagForm';
import { PATH_ADMIN, PATH_ADMIN_TAGS, pathForTag } from '@/site/paths';
import PhotoLightbox from '@/photo/PhotoLightbox';
import { getPhotosTagMeta } from '@/services/vercel-postgres';
import { getPhotosTagMeta } from '@/photo/db';
import AdminTagBadge from '@/admin/AdminTagBadge';
const MAX_PHOTO_TO_SHOW = 6;

View File

@ -10,7 +10,7 @@ import { MAX_PHOTOS_TO_SHOW_OG } from '@/image-response';
import { Metadata } from 'next/types';
import PhotoGridSidebar from '@/photo/PhotoGridSidebar';
import { getPhotoSidebarData } from '@/photo/data';
import { getPhotos } from '@/services/vercel-postgres';
import { getPhotos } from '@/photo/db';
import { cache } from 'react';
import PhotoGridInfinite from '@/photo/PhotoGridInfinite';

View File

@ -8,7 +8,7 @@ import { Metadata } from 'next/types';
import { MAX_PHOTOS_TO_SHOW_OG } from '@/image-response';
import PhotosLarge from '@/photo/PhotosLarge';
import { cache } from 'react';
import { getPhotos, getPhotosCount } from '@/services/vercel-postgres';
import { getPhotos, getPhotosCount } from '@/photo/db';
import PhotosLargeInfinite from '@/photo/PhotosLargeInfinite';
export const dynamic = 'force-static';

View File

@ -8,7 +8,7 @@ import {
sqlRenamePhotoTagGlobally,
getPhoto,
getPhotos,
} from '@/services/vercel-postgres';
} from '@/photo/db';
import {
PhotoFormData,
convertFormDataToPhotoDbInsert,

View File

@ -20,7 +20,7 @@ import {
getPhotosDateRange,
getPhotosNearId,
getPhotosMostRecentUpdate,
} from '@/services/vercel-postgres';
} from '@/photo/db';
import { parseCachedPhotoDates, parseCachedPhotosDates } from '@/photo';
import { createCameraKey } from '@/camera';
import {

View File

@ -9,7 +9,7 @@ import {
getUniqueCameras,
getUniqueFilmSimulations,
getUniqueTags,
} from '@/services/vercel-postgres';
} from '@/photo/db';
import { SHOW_FILM_SIMULATIONS } from '@/site/config';
import { sortTagsObject } from '@/tag';

View File

@ -1,4 +1,8 @@
import { db, sql } from '@vercel/postgres';
import {
sql,
directQuery,
convertArrayToPostgresString,
} from '@/services/postgres';
import {
PhotoDb,
PhotoDbInsert,
@ -16,10 +20,6 @@ import { screenForPPR } from '@/utility/ppr';
const PHOTO_DEFAULT_LIMIT = 100;
export const convertArrayToPostgresString = (array?: string[]) => array
? `{${array.join(',')}}`
: null;
const sqlCreatePhotosTable = () =>
sql`
CREATE TABLE IF NOT EXISTS photos (
@ -420,7 +420,7 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => {
values.push(limit, offset);
return safelyQueryPhotos(async () => {
return db.query(sql.join(' '), values);
return directQuery(sql.join(' '), values);
}, sql.join(' '))
.then(({ rows }) => rows.map(parsePhotoFromDb));
};
@ -434,7 +434,7 @@ export const getPhotosNearId = async (
: 'ORDER BY taken_at DESC';
return safelyQueryPhotos(async () => {
return db.query(
return directQuery(
`
WITH twi AS (
SELECT *, row_number()

46
src/services/postgres.ts Normal file
View File

@ -0,0 +1,46 @@
import { Client, QueryResultRow } from 'pg';
export type Primitive = string | number | boolean | undefined | null;
export const directQuery = async <T extends QueryResultRow = any>(
queryString: string,
values: Primitive[],
) => {
const client = new Client({
connectionString: process.env.POSTGRES_URL,
ssl: true,
});
await client.connect();
const response = await client.query<T>(queryString, values);
await client.end();
return response;
};
export const sql = <T extends QueryResultRow>(
strings: TemplateStringsArray,
...values: Primitive[]
) => {
if (!isTemplateStringsArray(strings) || !Array.isArray(values)) {
throw new Error('Invalid template literal argument');
}
let result = strings[0] ?? '';
for (let i = 1; i < strings.length; i++) {
result += `$${i}${strings[i] ?? ''}`;
}
return directQuery<T>(result, values);
};
export const convertArrayToPostgresString = (array?: string[]) => array
? `{${array.join(',')}}`
: null;
const isTemplateStringsArray = (
strings: unknown,
): strings is TemplateStringsArray => {
return (
Array.isArray(strings) && 'raw' in strings && Array.isArray(strings.raw)
);
};