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:
parent
d8918d2df1
commit
3ae9ced6fa
9 changed files with 750 additions and 46 deletions
3
packages/db/.env.example
Normal file
3
packages/db/.env.example
Normal 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
73
packages/db/README.md
Normal 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
26
packages/db/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
90
packages/db/prisma/schema.prisma
Normal file
90
packages/db/prisma/schema.prisma
Normal 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")
|
||||
}
|
||||
55
packages/db/prisma/seed.ts
Normal file
55
packages/db/prisma/seed.ts
Normal 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
15
packages/db/src/client.ts
Normal 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
2
packages/db/src/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { prisma } from './client';
|
||||
export * from '@prisma/client';
|
||||
11
packages/db/tsconfig.json
Normal file
11
packages/db/tsconfig.json
Normal 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
521
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue