feat(db): create database package with Prisma schema for NPM registry

- Add @tpmjs/db package with Prisma ORM setup
- Define Tool model with NPM metadata and TPMJS fields
- Define SyncCheckpoint model for tracking sync worker progress
- Define SyncLog model for audit trail of sync operations
- Add Prisma client singleton with dev logging
- Add seed script for initializing sync checkpoints
- Include comprehensive README with setup instructions

Package includes:
- Complete Prisma schema matching NPM_MIRROR.md spec
- Three models: Tool, SyncCheckpoint, SyncLog
- Indexes for performance on key fields
- TypeScript support via @tpmjs/tsconfig
- Scripts for db:migrate, db:push, db:studio, db:seed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-11-28 02:07:39 +10:00
parent d8918d2df1
commit 3ae9ced6fa
9 changed files with 750 additions and 46 deletions

3
packages/db/.env.example Normal file
View file

@ -0,0 +1,3 @@
# Neon Postgres connection string
# Get this from https://neon.tech/ after creating a project
DATABASE_URL="postgresql://user:password@host.neon.tech/dbname?sslmode=require"

73
packages/db/README.md Normal file
View file

@ -0,0 +1,73 @@
# @tpmjs/db
Database package for TPMJS NPM Registry. Provides Prisma ORM setup and database client for storing tool metadata.
## Setup
### 1. Create Neon Database
1. Go to https://neon.tech/
2. Create a new project
3. Copy the connection string
### 2. Configure Environment
```bash
cp .env.example .env
# Edit .env and add your DATABASE_URL
```
### 3. Run Migrations
```bash
pnpm db:migrate
pnpm db:seed
```
## Usage
```typescript
import { prisma } from '@tpmjs/db';
// Query tools
const tools = await prisma.tool.findMany({
where: {
category: 'web-scraping',
isOfficial: true,
},
orderBy: {
qualityScore: 'desc',
},
take: 10,
});
// Update sync checkpoint
await prisma.syncCheckpoint.update({
where: { source: 'changes-feed' },
data: {
checkpoint: {
sequence: '12345',
lastProcessed: new Date().toISOString(),
},
},
});
```
## Scripts
- `pnpm db:generate` - Generate Prisma client
- `pnpm db:push` - Push schema to database (for development)
- `pnpm db:migrate` - Create and run migrations (for production)
- `pnpm db:studio` - Open Prisma Studio GUI
- `pnpm db:seed` - Seed initial data
## Schema
### Tool
Stores NPM packages with TPMJS metadata from their `package.json`.
### SyncCheckpoint
Tracks progress of sync workers (changes feed, keyword search, metrics).
### SyncLog
Audit trail of all sync operations.

26
packages/db/package.json Normal file
View file

@ -0,0 +1,26 @@
{
"name": "@tpmjs/db",
"version": "0.0.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"db:generate": "prisma generate",
"db:push": "prisma db push",
"db:migrate": "prisma migrate dev",
"db:studio": "prisma studio",
"db:seed": "tsx prisma/seed.ts",
"type-check": "tsc --noEmit"
},
"dependencies": {
"@prisma/client": "^6.2.0"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"@types/node": "^22.10.2",
"prisma": "^6.2.0",
"tsx": "^4.19.2",
"typescript": "^5.9.3"
}
}

View file

@ -0,0 +1,90 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
/// Main tools table - stores all discovered NPM packages with TPMJS metadata
model Tool {
id String @id @default(cuid())
// NPM Metadata
npmPackageName String @unique @map("npm_package_name") @db.VarChar(214)
npmVersion String @map("npm_version") @db.VarChar(50)
npmPublishedAt DateTime @map("npm_published_at")
npmDescription String? @map("npm_description") @db.Text
npmRepository Json? @map("npm_repository") @db.JsonB
npmHomepage String? @map("npm_homepage") @db.Text
npmLicense String? @map("npm_license") @db.VarChar(50)
// TPMJS Metadata (from package.json tpmjs field)
category String @db.VarChar(50)
description String @db.Text
example String @db.Text
parameters Json? @db.JsonB
returns Json? @db.JsonB
authentication Json? @db.JsonB
pricing Json? @db.JsonB
frameworks String[] @db.Text
links Json? @db.JsonB
tags String[] @db.Text
status String? @db.VarChar(20)
aiAgent Json? @map("ai_agent") @db.JsonB
// Discovery Metadata
discoveryMethod String @map("discovery_method") @db.VarChar(20) // 'keyword' | 'changes-feed'
isOfficial Boolean @default(false) @map("is_official")
tier String @db.VarChar(20) // 'minimal' | 'rich'
// Metrics
npmDownloadsLastMonth Int? @default(0) @map("npm_downloads_last_month")
githubStars Int? @default(0) @map("github_stars")
qualityScore Decimal? @map("quality_score") @db.Decimal(3, 2) // 0.00 to 1.00
// Timestamps
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([category])
@@index([isOfficial])
@@index([qualityScore])
@@index([npmDownloadsLastMonth])
@@index([createdAt])
@@map("tools")
}
/// Sync checkpoints - tracks progress of sync workers
model SyncCheckpoint {
id String @id @default(cuid())
source String @unique @db.VarChar(50) // 'changes-feed' | 'keyword-search' | 'metrics'
checkpoint Json @db.JsonB // Stores last sequence, timestamp, etc.
updatedAt DateTime @updatedAt @map("updated_at")
@@map("sync_checkpoints")
}
/// Sync logs - audit trail of sync operations
model SyncLog {
id String @id @default(cuid())
source String @db.VarChar(50) // 'changes-feed' | 'keyword-search' | 'metrics'
status String @db.VarChar(20) // 'success' | 'error' | 'partial'
processed Int @default(0) // Number of packages processed
skipped Int @default(0) // Number of packages skipped
errors Int @default(0) // Number of errors encountered
message String? @db.Text // Error message or summary
metadata Json? @db.JsonB // Additional context
createdAt DateTime @default(now()) @map("created_at")
@@index([source])
@@index([status])
@@index([createdAt])
@@map("sync_logs")
}

View file

@ -0,0 +1,55 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
console.log('Seeding sync checkpoints...');
// Create initial sync checkpoints
await prisma.syncCheckpoint.upsert({
where: { source: 'changes-feed' },
update: {},
create: {
source: 'changes-feed',
checkpoint: {
sequence: '0',
lastProcessed: null,
},
},
});
await prisma.syncCheckpoint.upsert({
where: { source: 'keyword-search' },
update: {},
create: {
source: 'keyword-search',
checkpoint: {
lastRun: null,
totalProcessed: 0,
},
},
});
await prisma.syncCheckpoint.upsert({
where: { source: 'metrics' },
update: {},
create: {
source: 'metrics',
checkpoint: {
lastRun: null,
totalProcessed: 0,
},
},
});
console.log('Seed completed successfully');
}
main()
.catch((e) => {
console.error('Seed failed:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

15
packages/db/src/client.ts Normal file
View file

@ -0,0 +1,15 @@
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}

2
packages/db/src/index.ts Normal file
View file

@ -0,0 +1,2 @@
export { prisma } from './client';
export * from '@prisma/client';

11
packages/db/tsconfig.json Normal file
View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": ".",
"incremental": false,
"composite": false
},
"include": ["src", "prisma"],
"exclude": ["node_modules", "dist"]
}

521
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff