feat: update licensing changes

This commit is contained in:
Rohith Gilla 2025-11-29 10:32:14 +05:30
parent e817501b6b
commit 82058dc7a7
13 changed files with 176 additions and 2950 deletions

5
.gitignore vendored
View file

@ -1,2 +1,5 @@
node_modules/
dist/
dist/
# Business documentation (contains sensitive info)
docs/

47
LICENSE Normal file
View file

@ -0,0 +1,47 @@
MIT License
Copyright (c) 2024 Rohith Gilla
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## Commercial Use License
While the source code is MIT licensed, pre-built binaries require a license
for commercial use. Commercial use is defined as using data-peek for
revenue-generating or work-related activities within a for-profit organization
of two or more people.
### Free Use (No License Required)
- Personal projects and learning
- Open source contributors
- Students and educators
- Registered non-profit organizations
- Solo founders (company of one)
### License Required
- Developers at companies with 2+ people
- Freelancers billing clients
- Agencies using for client work
This licensing model is inspired by Yaak and other sustainable indie software
projects that balance open source values with sustainable development.
Learn more at https://data-peek.dev/license

View file

@ -7,42 +7,52 @@ const faqs = [
{
question: 'Is data-peek really free?',
answer:
'Yes! The free tier is fully functional with 2 connections, 50 query history items, and 3 tabs. No credit card required, no time limits. The Pro license unlocks unlimited everything plus advanced features like inline editing and query plans.',
'Yes! data-peek is free for personal use with all features unlocked. No credit card required, no time limits, no feature restrictions. A license is only required if you use it for commercial purposes at a for-profit company with 2+ people.',
},
{
question: 'What does "pay once, own forever" mean?',
question: 'What counts as commercial use?',
answer:
'Unlike subscription software, you pay $29 once and get a perpetual license. The license includes 1 year of updates. After that year, you can keep using the version you have forever, or renew for another year of updates at a discounted rate.',
'Commercial use means using data-peek for work-related activities in a for-profit organization of 2+ people. This includes developers at startups/companies, freelancers billing clients, and agencies doing client work. Solo founders (company of one) are free!',
},
{
question: 'How many devices can I use with one license?',
question: 'Is data-peek open source?',
answer:
'One Pro license includes 3 device activations. You can use data-peek on your work laptop, home computer, and one more device. Need more? Contact us for volume licensing.',
'Yes! The source code is MIT licensed on GitHub. You can view, modify, fork, and build it yourself for any purpose. Pre-built binaries require a license for commercial use — this is how we sustain development.',
},
{
question: 'Does data-peek work offline?',
question: 'What does "perpetual fallback" mean?',
answer:
"Absolutely. data-peek runs entirely on your machine. We validate licenses online during activation, but after that, you can work offline. There's a grace period for license revalidation.",
'When you buy a Pro license ($29/year), you get 1 year of updates. If you don\'t renew, you keep your current version forever — it doesn\'t stop working. You just won\'t receive future updates. Renew anytime to get the latest.',
},
{
question: 'What databases are supported?',
question: "I'm a student. Can I use it for free?",
answer:
"Currently, data-peek is PostgreSQL-only. We're laser-focused on making the best Postgres experience possible. MySQL and SQLite support are planned for future releases.",
'Absolutely! Students and educators can use data-peek for free, even for school projects. Just reach out on X (@gillarohith) or email gillarohith1@gmail.com and we\'ll hook you up with a free license. Learning should never have barriers.',
},
{
question: 'How does the honor system work?',
answer:
'We trust you. There\'s no DRM, no aggressive license checks, no "you\'ve been logged out" surprises. If you\'re using it commercially, we ask that you pay for a license. Inspired by Yaak and other sustainable indie software.',
},
{
question: 'How many devices can I use?',
answer:
'Each license includes 3 device activations. Use it on your work laptop, home computer, and one more device. Need more? Just reach out.',
},
{
question: 'Is my data safe?',
answer:
'Yes. data-peek never sends your data anywhere. All queries run directly from your machine to your database. Connection credentials are encrypted locally. We have no telemetry, no analytics, no tracking.',
'Yes. data-peek runs entirely on your machine. All queries go directly to your database — we never see your data. Connection credentials are encrypted locally. No telemetry, no analytics, no tracking.',
},
{
question: 'What databases are supported?',
answer:
'Currently PostgreSQL and MySQL. We\'re laser-focused on making the best database experience possible. SQLite and more databases are planned for future releases.',
},
{
question: 'Can I get a refund?',
answer:
"Yes, we offer a 14-day money-back guarantee. If data-peek isn't right for you, just email us and we'll refund your purchase, no questions asked.",
},
{
question: 'Do you offer team or enterprise licenses?',
answer:
"Not yet, but we're working on it! Cloud sync and team features are coming soon. Sign up for our newsletter to be notified when they launch.",
'Yes, 30-day money-back guarantee, no questions asked. If data-peek isn\'t right for you, just email us.',
},
]

View file

@ -57,7 +57,7 @@ export function Footer() {
<Github className="w-4 h-4" />
</Link>
<Link
href="https://twitter.com/datapeekapp"
href="https://x.com/gillarohith"
target="_blank"
rel="noopener noreferrer"
className="w-9 h-9 rounded-lg bg-[--color-surface] border border-[--color-border] flex items-center justify-center text-[--color-text-muted] hover:text-[--color-text-primary] hover:border-[--color-text-muted] transition-colors"

View file

@ -1,7 +1,7 @@
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { ArrowRight, Sparkles, Zap, Download } from 'lucide-react'
import { ArrowRight, Github, Zap, Download, Sparkles } from 'lucide-react'
export function Hero() {
return (
@ -28,12 +28,16 @@ export function Hero() {
<div className="relative z-10 max-w-7xl mx-auto px-6 pt-32 pb-20">
<div className="flex flex-col items-center text-center">
{/* Early Bird Badge */}
<div className="animate-fade-in-up">
<Badge variant="default" size="lg" className="mb-8">
{/* Early Bird + Open Source Badge */}
<div className="animate-fade-in-up flex flex-wrap items-center justify-center gap-3 mb-8">
<Badge variant="default" size="lg">
<Sparkles className="w-3.5 h-3.5 mr-1.5" />
Early Bird 70% off
</Badge>
<Badge variant="secondary" size="lg">
<Github className="w-3.5 h-3.5 mr-1.5" />
Open Source
</Badge>
</div>
{/* Main Headline */}
@ -52,7 +56,7 @@ export function Hero() {
style={{ fontFamily: 'var(--font-body)' }}
>
A lightning-fast PostgreSQL client for developers who value simplicity.
No bloat, no subscriptions, no BS.
Open source, free for personal use.
</p>
{/* Terminal-style feature highlight */}
@ -70,7 +74,7 @@ export function Hero() {
</span>
<span className="w-px h-4 bg-[--color-border]" />
<span className="flex items-center gap-2 text-sm">
<span className="text-[--color-text-muted]">pay once, own forever</span>
<span className="text-[--color-text-muted]">all features free</span>
</span>
</div>

View file

@ -1,23 +1,22 @@
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Check, X, Sparkles, ArrowRight } from 'lucide-react'
import { Check, Heart, Github, ArrowRight, Sparkles } from 'lucide-react'
const plans = [
{
name: 'Free',
name: 'Personal',
price: '$0',
period: 'forever',
description: 'Perfect for trying out data-peek',
description: 'All features, personal use only',
features: [
{ text: '2 database connections', included: true },
{ text: '50 query history items', included: true },
{ text: '3 editor tabs', included: true },
{ text: '1 schema for ER diagrams', included: true },
{ text: 'CSV/JSON export', included: true },
{ text: 'Inline data editing', included: false },
{ text: 'Query execution plans', included: false },
{ text: 'Updates', included: false },
{ text: 'All features unlocked', included: true },
{ text: 'Unlimited connections', included: true },
{ text: 'Unlimited query history', included: true },
{ text: 'All future updates', included: true },
{ text: 'Personal projects & learning', included: true },
{ text: 'Open source contributors', included: true },
{ text: 'Students & educators', included: true },
],
cta: 'Download Free',
href: '/download',
@ -27,19 +26,17 @@ const plans = [
name: 'Pro',
price: '$29',
originalPrice: '$99',
period: 'one-time',
description: 'For developers who need the full power',
period: 'year',
description: 'For commercial use at work',
badge: 'Early Bird — 70% off',
features: [
{ text: 'Unlimited connections', included: true },
{ text: 'Unlimited query history', included: true },
{ text: 'Unlimited tabs', included: true },
{ text: 'Unlimited ER diagrams', included: true },
{ text: 'CSV/JSON export', included: true },
{ text: 'Inline data editing', included: true },
{ text: 'Query execution plans', included: true },
{ text: '1 year of updates', included: true },
{ text: 'Everything in Personal', included: true },
{ text: 'Commercial use allowed', included: true },
{ text: 'Use at work & for clients', included: true },
{ text: '1 year of updates included', included: true },
{ text: '3 device activations', included: true },
{ text: 'Perpetual fallback license', included: true },
{ text: '30-day money-back guarantee', included: true },
],
cta: 'Get Pro License',
href: '#buy',
@ -66,15 +63,16 @@ export function Pricing() {
className="text-4xl md:text-5xl lg:text-6xl font-semibold tracking-tight mb-6"
style={{ fontFamily: 'var(--font-display)' }}
>
Pay once.
Open source.
<br />
<span className="text-[--color-text-secondary]">Own forever.</span>
<span className="text-[--color-text-secondary]">Pay for commercial use.</span>
</h2>
<p
className="text-lg text-[--color-text-secondary] max-w-xl mx-auto"
style={{ fontFamily: 'var(--font-body)' }}
>
No subscriptions, no recurring fees. One purchase, lifetime access.
Free for personal use. A license supports development and is required
for commercial use in for-profit organizations.
</p>
</div>
@ -92,7 +90,7 @@ export function Pricing() {
{/* Badge */}
{plan.badge && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
<Badge variant="default" size="md">
<Badge variant="default" size="md" className="whitespace-nowrap">
<Sparkles className="w-3 h-3 mr-1" />
{plan.badge}
</Badge>
@ -128,22 +126,10 @@ export function Pricing() {
<ul className="space-y-3 mb-8">
{plan.features.map((feature) => (
<li key={feature.text} className="flex items-center gap-3">
{feature.included ? (
<div className="w-5 h-5 rounded-full bg-[--color-success]/10 flex items-center justify-center flex-shrink-0">
<Check className="w-3 h-3 text-[--color-success]" />
</div>
) : (
<div className="w-5 h-5 rounded-full bg-[--color-text-muted]/10 flex items-center justify-center flex-shrink-0">
<X className="w-3 h-3 text-[--color-text-muted]" />
</div>
)}
<span
className={`text-sm ${
feature.included
? 'text-[--color-text-primary]'
: 'text-[--color-text-muted]'
}`}
>
<div className="w-5 h-5 rounded-full bg-[--color-success]/10 flex items-center justify-center flex-shrink-0">
<Check className="w-3 h-3 text-[--color-success]" />
</div>
<span className="text-sm text-[--color-text-primary]">
{feature.text}
</span>
</li>
@ -166,20 +152,69 @@ export function Pricing() {
))}
</div>
{/* Cloud Teaser */}
<div className="mt-12 text-center">
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[--color-surface] border border-[--color-border]">
<span
className="text-sm text-[--color-text-muted]"
style={{ fontFamily: 'var(--font-mono)' }}
>
Coming soon:
</span>
<span className="text-sm text-[--color-text-secondary]">
Cloud sync & team features
</span>
{/* Honor System Notice */}
<div className="mt-12 p-6 rounded-xl bg-[--color-surface] border border-[--color-border] max-w-2xl mx-auto">
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-lg bg-[--color-accent]/10 flex items-center justify-center flex-shrink-0">
<Heart className="w-5 h-5 text-[--color-accent]" />
</div>
<div>
<h4
className="text-base font-medium mb-2"
style={{ fontFamily: 'var(--font-display)' }}
>
Honor System Licensing
</h4>
<p className="text-sm text-[--color-text-secondary] mb-3">
Inspired by{' '}
<Link
href="https://yaak.app"
target="_blank"
rel="noopener noreferrer"
className="text-[--color-accent] hover:underline"
>
Yaak
</Link>{' '}
and other sustainable indie software. No DRM, no aggressive enforcement.
We trust you to do the right thing.
</p>
<p className="text-sm text-[--color-text-secondary]">
<strong>Students:</strong> Use it free! Just reach out for a free license.
</p>
<p className="text-sm text-[--color-text-muted] mt-2">
Questions?{' '}
<Link
href="https://x.com/gillarohith"
target="_blank"
rel="noopener noreferrer"
className="text-[--color-accent] hover:underline"
>
@gillarohith
</Link>{' '}
or{' '}
<Link
href="mailto:gillarohith1@gmail.com"
className="text-[--color-accent] hover:underline"
>
gillarohith1@gmail.com
</Link>
</p>
</div>
</div>
</div>
{/* Open Source Notice */}
<div className="mt-6 text-center">
<Link
href="https://github.com/Rohithgilla12/data-peek"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[--color-surface] border border-[--color-border] text-sm text-[--color-text-secondary] hover:text-[--color-text-primary] hover:border-[--color-text-muted] transition-colors"
>
<Github className="w-4 h-4" />
<span>View source on GitHub MIT Licensed</span>
</Link>
</div>
</div>
</section>
)

View file

@ -1,570 +0,0 @@
# Table Designer Feature - Implementation Plan
## Overview
This plan outlines the implementation of a comprehensive Table Designer feature for data-peek, enabling users to create new tables and modify existing tables through a dedicated tab interface with full PostgreSQL feature support.
## Scope
- **Create Table**: Full-featured table creation with columns, constraints, indexes
- **Edit Table (ALTER TABLE)**: Add/drop/rename columns, modify types, manage constraints and indexes
- **PostgreSQL Full Features**: Sequences, partitioning, inheritance, custom types, CHECK constraints, UNIQUE constraints
---
## Phase 1: Shared Types & Data Models
### File: `packages/shared/src/index.ts`
Add new types for DDL operations:
```typescript
// PostgreSQL data types for dropdown
export type PostgresDataType =
| 'smallint' | 'integer' | 'bigint' | 'serial' | 'bigserial'
| 'numeric' | 'real' | 'double precision' | 'money'
| 'char' | 'varchar' | 'text'
| 'bytea'
| 'timestamp' | 'timestamptz' | 'date' | 'time' | 'timetz' | 'interval'
| 'boolean'
| 'uuid'
| 'json' | 'jsonb'
| 'xml'
| 'point' | 'line' | 'lseg' | 'box' | 'path' | 'polygon' | 'circle'
| 'cidr' | 'inet' | 'macaddr'
| 'int4range' | 'int8range' | 'numrange' | 'tsrange' | 'tstzrange' | 'daterange'
| 'custom' // For custom/enum types
// Column definition for table designer
export interface ColumnDefinition {
id: string // Client-side tracking
name: string
dataType: PostgresDataType | string
length?: number // For varchar(n), char(n)
precision?: number // For numeric(p,s)
scale?: number
isNullable: boolean
isPrimaryKey: boolean
isUnique: boolean
defaultValue?: string
defaultType?: 'value' | 'expression' | 'sequence'
sequenceName?: string // For nextval('sequence')
checkConstraint?: string
comment?: string
collation?: string
isArray?: boolean // For array types like text[]
}
// Constraint types
export type ConstraintType = 'primary_key' | 'foreign_key' | 'unique' | 'check' | 'exclude'
export interface ConstraintDefinition {
id: string
name?: string // Optional, auto-generated if not provided
type: ConstraintType
columns: string[]
// Foreign key specific
referencedSchema?: string
referencedTable?: string
referencedColumns?: string[]
onUpdate?: 'NO ACTION' | 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'SET DEFAULT'
onDelete?: 'NO ACTION' | 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'SET DEFAULT'
// Check constraint specific
checkExpression?: string
// Exclude constraint specific (advanced)
excludeElements?: Array<{ column: string; operator: string }>
excludeUsing?: 'btree' | 'gist' | 'gin' | 'hash'
}
// Index definition
export interface IndexDefinition {
id: string
name?: string
columns: Array<{
name: string
order?: 'ASC' | 'DESC'
nullsPosition?: 'FIRST' | 'LAST'
}>
isUnique: boolean
method?: 'btree' | 'hash' | 'gist' | 'gin' | 'spgist' | 'brin'
where?: string // Partial index condition
include?: string[] // INCLUDE columns (covering index)
concurrent?: boolean
}
// Table partitioning
export interface PartitionDefinition {
type: 'RANGE' | 'LIST' | 'HASH'
columns: string[]
}
// Full table definition
export interface TableDefinition {
schema: string
name: string
columns: ColumnDefinition[]
constraints: ConstraintDefinition[]
indexes: IndexDefinition[]
partition?: PartitionDefinition
inherits?: string[] // Parent tables
tablespace?: string
comment?: string
withOids?: boolean
unlogged?: boolean
}
// ALTER TABLE operations
export type AlterColumnOperation =
| { type: 'add'; column: ColumnDefinition }
| { type: 'drop'; columnName: string; cascade?: boolean }
| { type: 'rename'; oldName: string; newName: string }
| { type: 'set_type'; columnName: string; newType: string; using?: string }
| { type: 'set_nullable'; columnName: string; nullable: boolean }
| { type: 'set_default'; columnName: string; defaultValue: string | null }
| { type: 'set_comment'; columnName: string; comment: string | null }
export type AlterConstraintOperation =
| { type: 'add_constraint'; constraint: ConstraintDefinition }
| { type: 'drop_constraint'; name: string; cascade?: boolean }
| { type: 'rename_constraint'; oldName: string; newName: string }
export type AlterIndexOperation =
| { type: 'create_index'; index: IndexDefinition }
| { type: 'drop_index'; name: string; cascade?: boolean; concurrent?: boolean }
| { type: 'rename_index'; oldName: string; newName: string }
| { type: 'reindex'; name: string; concurrent?: boolean }
export interface AlterTableBatch {
schema: string
table: string
columnOperations: AlterColumnOperation[]
constraintOperations: AlterConstraintOperation[]
indexOperations: AlterIndexOperation[]
renameTable?: string
setSchema?: string
comment?: string | null
}
// Result types
export interface DDLResult {
success: boolean
executedSql: string[]
errors?: string[]
}
// Metadata for UI
export interface SequenceInfo {
schema: string
name: string
dataType: string
startValue: string
increment: string
}
export interface CustomTypeInfo {
schema: string
name: string
type: 'enum' | 'composite' | 'range' | 'domain'
values?: string[] // For enums
}
```
---
## Phase 2: DDL SQL Builder
### New File: `apps/desktop/src/main/ddl-builder.ts`
Create a dedicated builder for DDL statements:
```typescript
// Key functions to implement:
export function buildCreateTable(definition: TableDefinition): ParameterizedQuery
export function buildAlterTable(batch: AlterTableBatch): ParameterizedQuery[]
export function buildDropTable(schema: string, table: string, cascade?: boolean): ParameterizedQuery
export function buildCreateIndex(schema: string, table: string, index: IndexDefinition): ParameterizedQuery
export function buildPreviewDDL(definition: TableDefinition): string // For UI preview
export function buildAlterPreviewDDL(batch: AlterTableBatch): string[]
```
Key considerations:
- Quote all identifiers properly
- Handle reserved words
- Escape string values in defaults
- Support all PostgreSQL constraint syntax
- Handle partial indexes with WHERE
- Support expression indexes
---
## Phase 3: IPC Handlers
### File: `apps/desktop/src/main/index.ts`
Add new IPC handlers:
```typescript
// Create table
ipcMain.handle('db:create-table', async (_, { config, definition }) => {
// Execute CREATE TABLE statement
// Return DDLResult
})
// Alter table
ipcMain.handle('db:alter-table', async (_, { config, batch }) => {
// Execute ALTER TABLE statements in transaction
// Return DDLResult
})
// Drop table
ipcMain.handle('db:drop-table', async (_, { config, schema, table, cascade }) => {
// Execute DROP TABLE
// Return DDLResult
})
// Get table DDL (reverse engineer)
ipcMain.handle('db:get-table-ddl', async (_, { config, schema, table }) => {
// Query pg_catalog to reconstruct CREATE TABLE
// Return TableDefinition
})
// Get available sequences
ipcMain.handle('db:get-sequences', async (_, config) => {
// Query pg_sequences
// Return SequenceInfo[]
})
// Get custom types
ipcMain.handle('db:get-types', async (_, config) => {
// Query pg_type for enums, composites, etc.
// Return CustomTypeInfo[]
})
// Preview DDL without executing
ipcMain.handle('db:preview-ddl', async (_, { definition }) => {
// Return generated SQL string
})
```
### File: `apps/desktop/src/preload/index.ts`
Update API exposure:
```typescript
const api = {
// ... existing
ddl: {
createTable: (config, definition) => ipcRenderer.invoke('db:create-table', { config, definition }),
alterTable: (config, batch) => ipcRenderer.invoke('db:alter-table', { config, batch }),
dropTable: (config, schema, table, cascade) =>
ipcRenderer.invoke('db:drop-table', { config, schema, table, cascade }),
getTableDDL: (config, schema, table) =>
ipcRenderer.invoke('db:get-table-ddl', { config, schema, table }),
getSequences: (config) => ipcRenderer.invoke('db:get-sequences', config),
getTypes: (config) => ipcRenderer.invoke('db:get-types', config),
previewDDL: (definition) => ipcRenderer.invoke('db:preview-ddl', { definition })
}
}
```
---
## Phase 4: Tab Store Extension
### File: `apps/desktop/src/renderer/src/stores/tab-store.ts`
Add new tab type:
```typescript
export type TabType = 'query' | 'table-preview' | 'erd' | 'table-designer'
export interface TableDesignerTab extends BaseTab {
type: 'table-designer'
schemaName: string
tableName?: string // undefined for new table
mode: 'create' | 'edit'
isDirty: boolean
}
// New actions
createTableDesignerTab: (connectionId: string, schemaName: string, tableName?: string) => string
```
---
## Phase 5: DDL Store
### New File: `apps/desktop/src/renderer/src/stores/ddl-store.ts`
Create Zustand store for table designer state:
```typescript
interface DDLState {
// Per-tab state (keyed by tabId)
tabState: Map<string, TableDesignerState>
// Actions
initializeTab: (tabId: string, mode: 'create' | 'edit', definition?: TableDefinition) => void
// Column operations
addColumn: (tabId: string, column?: Partial<ColumnDefinition>) => void
updateColumn: (tabId: string, columnId: string, updates: Partial<ColumnDefinition>) => void
removeColumn: (tabId: string, columnId: string) => void
reorderColumns: (tabId: string, startIndex: number, endIndex: number) => void
// Constraint operations
addConstraint: (tabId: string, constraint: ConstraintDefinition) => void
updateConstraint: (tabId: string, constraintId: string, updates: Partial<ConstraintDefinition>) => void
removeConstraint: (tabId: string, constraintId: string) => void
// Index operations
addIndex: (tabId: string, index: IndexDefinition) => void
updateIndex: (tabId: string, indexId: string, updates: Partial<IndexDefinition>) => void
removeIndex: (tabId: string, indexId: string) => void
// Table properties
updateTableProperties: (tabId: string, updates: Partial<TableDefinition>) => void
// Diff tracking for edit mode
getAlterTableBatch: (tabId: string) => AlterTableBatch | null
// Validation
validateDefinition: (tabId: string) => { valid: boolean; errors: string[] }
// Cleanup
clearTabState: (tabId: string) => void
}
interface TableDesignerState {
original?: TableDefinition // For edit mode - track original state
current: TableDefinition
isDirty: boolean
validationErrors: string[]
}
```
---
## Phase 6: UI Components
### New Components Structure:
```
src/renderer/src/components/
├── table-designer/
│ ├── table-designer.tsx # Main container
│ ├── column-list.tsx # Column grid with drag-drop
│ ├── column-editor-row.tsx # Inline column editing
│ ├── column-editor-dialog.tsx # Full column editor modal
│ ├── constraint-list.tsx # Constraint management
│ ├── constraint-editor-dialog.tsx # Add/edit constraint
│ ├── index-list.tsx # Index management
│ ├── index-editor-dialog.tsx # Add/edit index
│ ├── table-properties.tsx # Table-level settings
│ ├── ddl-preview-panel.tsx # Live SQL preview
│ ├── data-type-select.tsx # Type picker with search
│ └── sequence-select.tsx # Sequence picker
```
### Main Component: `table-designer.tsx`
Layout:
```
┌─────────────────────────────────────────────────────────────────┐
│ Toolbar: [Save] [Preview SQL] [Cancel] | Table: schema.name │
├─────────────────────────────────────────────────────────────────┤
│ Tabs: [Columns] [Constraints] [Indexes] [Properties] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Column Grid (primary view) │ │
│ │ ┌─────┬──────────┬──────────┬────┬────┬────┬─────────┐ │ │
│ │ │ ⋮⋮ │ Name │ Type │ PK │ NN │ UQ │ Default │ │ │
│ │ ├─────┼──────────┼──────────┼────┼────┼────┼─────────┤ │ │
│ │ │ ⋮⋮ │ id │ uuid │ ✓ │ ✓ │ │ gen_... │ │ │
│ │ │ ⋮⋮ │ name │ varchar │ │ ✓ │ │ │ │ │
│ │ │ ⋮⋮ │ email │ varchar │ │ ✓ │ ✓ │ │ │ │
│ │ │ ⋮⋮ │ created │ timestamp│ │ ✓ │ │ now() │ │ │
│ │ └─────┴──────────┴──────────┴────┴────┴────┴─────────┘ │ │
│ │ [+ Add Column] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ DDL Preview (collapsible) │ │
│ │ CREATE TABLE schema.table ( │ │
│ │ id uuid PRIMARY KEY DEFAULT gen_random_uuid(), │ │
│ │ ... │ │
│ │ ); │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### Features by Tab:
**Columns Tab:**
- Drag-drop reordering
- Inline editing for quick changes
- Click to expand for advanced options
- Visual indicators: PK (key icon), NOT NULL (*), UNIQUE (U)
- Type suggestions as you type
- Array type toggle
- Collation selector for text types
**Constraints Tab:**
- List view with constraint type badges
- Add constraint button with type selector
- Foreign key builder with table/column pickers
- Check constraint with SQL editor
- Unique constraint with column multi-select
**Indexes Tab:**
- List of indexes with method badges
- Column selector with order (ASC/DESC)
- Partial index condition editor
- INCLUDE column selector
- CONCURRENT option toggle
**Properties Tab:**
- Table comment
- Schema selector (for changing schema)
- Tablespace selector
- Partition configuration
- Inheritance configuration
- UNLOGGED toggle
---
## Phase 7: Integration Points
### Schema Explorer Updates
Add to `schema-explorer.tsx`:
1. "Create Table" button in schema header:
```tsx
<Button onClick={() => createTableDesignerTab(connectionId, schema.name)}>
<Plus className="size-3.5" />
</Button>
```
2. Context menu on tables:
```tsx
<ContextMenu>
<ContextMenuItem onClick={() => createTableDesignerTab(connectionId, schema.name, table.name)}>
Edit Table Structure
</ContextMenuItem>
<ContextMenuItem onClick={() => handleDropTable(schema.name, table.name)}>
Drop Table...
</ContextMenuItem>
</ContextMenu>
```
### Tab Container Updates
Add to `tab-container.tsx`:
```tsx
case 'table-designer':
return <TableDesigner tabId={tab.id} />
```
---
## Phase 8: Implementation Order
### Step 1: Foundation (Day 1-2)
1. Add all shared types to `packages/shared/src/index.ts`
2. Create `ddl-builder.ts` with CREATE TABLE generation
3. Add basic IPC handlers for create-table and preview-ddl
### Step 2: Basic UI (Day 2-3)
1. Create DDL store
2. Add new tab type to tab-store
3. Create basic TableDesigner component with column list
4. Add column editing (inline + dialog)
5. Add DDL preview panel
### Step 3: Column Features (Day 3-4)
1. Implement all column properties
2. Add data type selector with all PostgreSQL types
3. Add sequence picker for defaults
4. Implement column validation
### Step 4: Constraints (Day 4-5)
1. Add constraint list UI
2. Implement PRIMARY KEY constraint
3. Implement FOREIGN KEY with table/column picker
4. Implement UNIQUE and CHECK constraints
### Step 5: Indexes (Day 5)
1. Add index list UI
2. Implement index creation with all options
3. Add partial index support
### Step 6: ALTER TABLE (Day 6-7)
1. Implement `db:get-table-ddl` handler (reverse engineer table)
2. Implement diff tracking in DDL store
3. Generate ALTER TABLE statements
4. Handle column type changes with USING
### Step 7: Advanced Features (Day 7-8)
1. Add partitioning support
2. Add inheritance support
3. Add custom type support
4. Add schema explorer integration (create button, context menu)
### Step 8: Polish (Day 8)
1. Validation and error handling
2. Keyboard shortcuts
3. Undo/redo support
4. Test with various edge cases
---
## Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| `Cmd+S` | Save/Execute DDL |
| `Cmd+Enter` | Preview SQL |
| `Cmd+N` | Add new column |
| `Delete` | Remove selected column |
| `Cmd+Z` | Undo last change |
| `Cmd+Shift+Z` | Redo |
| `Tab` | Move to next column field |
| `Shift+Tab` | Move to previous field |
---
## Validation Rules
1. **Table name**: Required, valid identifier, not reserved word
2. **Column names**: Required, unique within table, valid identifier
3. **Primary key**: At most one (can be composite)
4. **Foreign keys**: Referenced table must exist, column types must match
5. **Check constraints**: Valid SQL expression
6. **Default values**: Type-compatible with column
---
## Error Handling
1. **Pre-execution validation**: Show errors inline before attempting execution
2. **Execution errors**: Display PostgreSQL error message clearly
3. **Partial success**: If ALTER TABLE fails mid-batch, show which operations succeeded
4. **Recovery**: Provide option to rollback or continue
---
## Testing Scenarios
1. Create simple table with various column types
2. Create table with composite primary key
3. Create table with foreign keys to multiple tables
4. Create table with CHECK constraints
5. Create table with partial indexes
6. Edit existing table - add column
7. Edit existing table - change column type
8. Edit existing table - add/remove constraints
9. Drop table with cascade
10. Create table with partitioning

View file

@ -1,511 +0,0 @@
# data-peek Backend & Business Plan
> Planning document for the data-peek licensing system, payment integration, and marketing site.
---
## Business Model
### Pricing Strategy
| Tier | Price | What's Included |
|------|-------|-----------------|
| **Pro License** | ~~$99~~ **$29** (Early Bird) | Perpetual license, 1 year of updates, 3 device activations |
| **Free Tier** | $0 | Limited features (see below) |
| **Cloud** (Future) | ~$5-8/month | Connection sync, saved queries, team features |
### Early Bird Promotion
- **Regular Price:** $99
- **Launch Price:** $29 (Dec 2024 / Early Adopters)
- **Messaging:** "70% off for early supporters"
### Free vs Pro Features
| Feature | Free | Pro ($29) |
|---------|------|-----------|
| Connections | 2 | Unlimited |
| Query History | 50 queries | Unlimited |
| Editor Tabs | 3 | Unlimited |
| Export CSV/JSON | Yes | Yes |
| ER Diagrams | 1 schema | Unlimited |
| Inline Editing | View only | Full CRUD |
| Query Execution Plans | No | Yes |
| Updates | No | 1 year |
| Device Activations | 1 | 3 |
---
## Tech Stack
| Component | Technology | Why |
|-----------|------------|-----|
| Marketing Site | Next.js 14 (App Router) | SSR, API routes, great DX |
| Database | PostgreSQL (Supabase or Neon) | Familiar, reliable |
| Auth | NextAuth.js | Easy OAuth + credentials |
| Payments | DodoPayments | MoR, global reach, one-time + subscriptions |
| Email | Resend | Developer-friendly, good deliverability |
| Hosting | Vercel | Seamless Next.js deployment |
| File Storage | Cloudflare R2 or S3 | App binary downloads |
| Analytics | Plausible or PostHog | Privacy-friendly |
---
## DodoPayments Integration
### Why DodoPayments
- Merchant of Record (handles taxes, VAT globally)
- 150+ countries, 80+ currencies
- One-time payments support (perfect for perpetual licenses)
- SDKs for TypeScript/Node.js
- Webhook support for automation
### Product Setup in DodoPayments
```
Product: data-peek Pro License
Type: One-time payment
Price: $29 (promo) / $99 (regular)
Metadata:
- license_type: "pro"
- updates_duration: "1_year"
- max_activations: 3
```
### Webhook Events to Handle
| Event | Action |
|-------|--------|
| `payment.completed` | Generate license key, store in DB, send email |
| `payment.refunded` | Revoke license, update status |
| `payment.failed` | Log for debugging, notify if needed |
---
## Database Schema
### Tables
```sql
-- Customers (synced from DodoPayments)
CREATE TABLE customers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
name TEXT,
dodo_customer_id TEXT UNIQUE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Licenses
CREATE TABLE licenses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID REFERENCES customers(id),
license_key TEXT UNIQUE NOT NULL,
plan TEXT NOT NULL DEFAULT 'pro',
status TEXT NOT NULL DEFAULT 'active', -- active, revoked, expired
max_activations INT NOT NULL DEFAULT 3,
dodo_payment_id TEXT UNIQUE,
dodo_product_id TEXT,
purchased_at TIMESTAMPTZ DEFAULT NOW(),
updates_until TIMESTAMPTZ NOT NULL, -- purchased_at + 1 year
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Device Activations
CREATE TABLE activations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
license_id UUID REFERENCES licenses(id) ON DELETE CASCADE,
device_id TEXT NOT NULL, -- Hardware fingerprint
device_name TEXT, -- e.g., "MacBook Pro M2"
os TEXT, -- macos, windows, linux
app_version TEXT,
activated_at TIMESTAMPTZ DEFAULT NOW(),
last_validated_at TIMESTAMPTZ DEFAULT NOW(),
is_active BOOLEAN DEFAULT TRUE,
UNIQUE(license_id, device_id)
);
-- App Releases (for update checks)
CREATE TABLE releases (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
version TEXT UNIQUE NOT NULL, -- semver: 1.0.0
release_notes TEXT,
download_url_mac TEXT,
download_url_mac_arm TEXT,
download_url_windows TEXT,
download_url_linux TEXT,
is_latest BOOLEAN DEFAULT FALSE,
min_supported_version TEXT, -- for forced updates
released_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes
CREATE INDEX idx_licenses_customer ON licenses(customer_id);
CREATE INDEX idx_licenses_key ON licenses(license_key);
CREATE INDEX idx_activations_license ON activations(license_id);
CREATE INDEX idx_activations_device ON activations(device_id);
```
---
## API Endpoints
### Public API (called by Electron app)
```
POST /api/license/validate
Request: { licenseKey, deviceId }
Response: { valid, plan, status, updatesUntil, activationsRemaining }
POST /api/license/activate
Request: { licenseKey, deviceId, deviceName, os, appVersion }
Response: { success, activation, license }
POST /api/license/deactivate
Request: { licenseKey, deviceId }
Response: { success, activationsRemaining }
GET /api/updates/check?version=1.0.0&platform=macos
Response: { hasUpdate, latestVersion, downloadUrl, releaseNotes, forceUpdate }
```
### Webhook Endpoint
```
POST /api/webhooks/dodo
- Verify signature from DodoPayments
- Handle payment.completed → create license
- Handle payment.refunded → revoke license
```
### Protected API (requires auth, for account dashboard)
```
GET /api/account/licenses # List user's licenses
GET /api/account/activations # List active devices
POST /api/account/deactivate # Deactivate a device remotely
GET /api/account/downloads # Get download links
```
---
## License Key Generation
### Format
```
DPRO-XXXX-XXXX-XXXX-XXXX
```
- Prefix: `DPRO` (data-peek pro)
- 4 groups of 4 alphanumeric characters
- Total: 20 characters (easy to type, hard to guess)
### Generation Logic
```typescript
import { randomBytes } from 'crypto'
function generateLicenseKey(): string {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' // No 0, O, 1, I (avoid confusion)
const segments: string[] = []
for (let i = 0; i < 4; i++) {
let segment = ''
for (let j = 0; j < 4; j++) {
const randomIndex = randomBytes(1)[0] % chars.length
segment += chars[randomIndex]
}
segments.push(segment)
}
return `DPRO-${segments.join('-')}`
}
```
---
## Electron App Integration
### License Activation Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ FIRST LAUNCH │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ Show │ │ User enters │ │ Call /activate │ │
│ │ License │───▶│ license key │───▶│ API endpoint │ │
│ │ Dialog │ │ │ │ │ │
│ └─────────────┘ └──────────────┘ └────────┬────────┘ │
│ │ │
│ ┌────────────────────────┴──────┐ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌───────────┐ │
│ │ Valid │ │ Invalid │ │
│ │ License │ │ Show │ │
│ │ Store │ │ Error │ │
│ │ locally │ │ │ │
│ └─────────────┘ └───────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Validation Strategy
```typescript
// On app startup
async function checkLicense(): Promise<LicenseStatus> {
const stored = await getLicenseFromStore()
if (!stored) {
return { status: 'free', features: FREE_FEATURES }
}
// Try online validation
try {
const result = await validateOnline(stored.key, getDeviceId())
if (result.valid) {
// Update local cache
await updateLicenseCache(result)
return { status: 'pro', features: PRO_FEATURES, license: result }
} else {
// License revoked or invalid
await clearLicenseStore()
return { status: 'free', features: FREE_FEATURES, error: result.reason }
}
} catch (error) {
// Offline - use cached validation (grace period)
if (stored.cachedValidation && isWithinGracePeriod(stored.lastValidated)) {
return { status: 'pro', features: PRO_FEATURES, offline: true }
}
return { status: 'free', features: FREE_FEATURES }
}
}
```
### Device Fingerprinting
```typescript
import { machineIdSync } from 'node-machine-id'
import os from 'os'
function getDeviceId(): string {
return machineIdSync() // Unique per machine
}
function getDeviceName(): string {
return os.hostname()
}
function getDeviceInfo() {
return {
deviceId: getDeviceId(),
deviceName: getDeviceName(),
os: process.platform, // darwin, win32, linux
appVersion: app.getVersion()
}
}
```
---
## Marketing Site Structure
### Pages
```
/ # Landing page
/pricing # Pricing with buy button
/download # Platform-specific downloads
/docs # Getting started guide
/docs/[slug] # Individual doc pages
/faq # Frequently asked questions
/changelog # Release history
/blog # Updates, tutorials (future)
/account # Dashboard (protected)
/account/licenses # Manage licenses (protected)
/account/downloads # Download links (protected)
/login # Auth page
```
### Landing Page Sections
1. **Hero** - Tagline, screenshot, CTA button
2. **Problem** - Pain points with existing tools
3. **Features** - Key capabilities with visuals
4. **Demo** - GIF or video of the app in action
5. **Comparison** - vs pgAdmin, DBeaver, TablePlus
6. **Pricing** - Single tier, early bird callout
7. **FAQ** - Common questions
8. **CTA** - Final call to action
---
## Email Templates
### Welcome Email (after purchase)
```
Subject: Your data-peek Pro license 🎉
Hi {name},
Thank you for purchasing data-peek Pro!
Your license key: {license_key}
Quick start:
1. Download data-peek: {download_link}
2. Open the app and go to Settings → License
3. Enter your license key
Your license includes:
✓ 1 year of updates (until {updates_until})
✓ 3 device activations
✓ All Pro features unlocked
Need help? Reply to this email.
Happy querying!
— The data-peek team
```
### License Activation Email
```
Subject: data-peek activated on {device_name}
Your license was just activated on a new device:
Device: {device_name}
Activated: {timestamp}
Activations used: {used}/{max}
Not you? Manage your devices at {dashboard_link}
```
---
## Implementation Roadmap
### Phase 1: MVP (Week 1)
- [ ] Set up Next.js project with App Router
- [ ] Configure DodoPayments account and product
- [ ] Create database schema (Supabase/Neon)
- [ ] Implement `/api/webhooks/dodo` endpoint
- [ ] Implement `/api/license/validate` endpoint
- [ ] Implement `/api/license/activate` endpoint
- [ ] Build landing page with pricing
- [ ] Build download page
- [ ] Deploy to Vercel
- [ ] Test end-to-end purchase flow
### Phase 2: Polish (Week 2)
- [ ] Add account dashboard with NextAuth
- [ ] Implement device management UI
- [ ] Add email notifications (Resend)
- [ ] Implement update checker API
- [ ] Add analytics (Plausible)
- [ ] Write FAQ page
- [ ] Add basic docs/getting started
### Phase 3: Electron Integration (Week 2-3)
- [ ] Add license dialog to data-peek app
- [ ] Implement device fingerprinting
- [ ] Add license validation on startup
- [ ] Implement feature gating (free vs pro)
- [ ] Add "Check for updates" functionality
- [ ] Handle offline grace period
- [ ] Test activation/deactivation flow
### Phase 4: Launch Prep (Week 3)
- [ ] Create app store screenshots
- [ ] Record demo video/GIF
- [ ] Write launch blog post
- [ ] Set up social media accounts
- [ ] Prepare ProductHunt launch
- [ ] Build releases for all platforms
- [ ] Final QA testing
---
## Security Considerations
### API Security
- [ ] Rate limiting on all endpoints (prevent brute force)
- [ ] Webhook signature verification (DodoPayments)
- [ ] Input validation on all parameters
- [ ] SQL injection prevention (parameterized queries)
### License Security
- [ ] License keys stored hashed in DB? (or encrypted)
- [ ] Device ID cannot be spoofed easily
- [ ] Offline grace period limited (7-14 days)
- [ ] Anomaly detection (too many activations)
### Data Privacy
- [ ] Minimal data collection
- [ ] No tracking in the app
- [ ] Clear privacy policy
- [ ] GDPR compliance (if EU customers)
---
## Metrics to Track
### Business Metrics
- Total licenses sold
- Revenue (MRR if cloud tier added)
- Conversion rate (free → pro)
- Refund rate
### Product Metrics
- Daily/weekly active users
- Feature usage (which features are popular)
- Error rates
- Update adoption rate
### Marketing Metrics
- Website visitors
- Download counts
- Trial starts
- Email open/click rates
---
## Future Considerations
### Cloud Tier (v2)
- User accounts required
- Connection sync across devices
- Saved queries library
- Team sharing features
- Subscription billing via DodoPayments
### Additional Payment Options
- Regional pricing
- Team/volume licenses
- Educational discounts
- Lifetime deals (AppSumo?)
---
## Open Questions
- [ ] Exact DodoPayments webhook event names (check their docs)
- [ ] License key format preference
- [ ] Offline grace period duration (7 or 14 days?)
- [ ] Domain name for marketing site
- [ ] App signing certificates (macOS notarization, Windows signing)
---
## Resources
- [DodoPayments Docs](https://docs.dodopayments.com)
- [Next.js App Router](https://nextjs.org/docs/app)
- [NextAuth.js](https://next-auth.js.org/)
- [Resend](https://resend.com/docs)
- [Supabase](https://supabase.com/docs)
- [node-machine-id](https://www.npmjs.com/package/node-machine-id)
---
*Document created: November 2024*
*Last updated: November 2024*

View file

@ -1,279 +0,0 @@
# data-peek Feature Overview
> A minimal, fast, beautiful PostgreSQL client for developers who want to quickly peek at their data.
---
## Product Summary
**data-peek** is a lightweight desktop database client designed for developers who need quick, frictionless access to their PostgreSQL databases. Unlike bloated alternatives like pgAdmin or DBeaver, data-peek focuses on speed, simplicity, and a keyboard-first experience.
**Target Audience:** Developers, data engineers, backend engineers, and anyone who needs to quickly query and explore PostgreSQL databases without the overhead of enterprise tools.
**Platforms:** macOS (Apple Silicon + Intel), Windows, Linux
---
## Key Value Propositions
| Benefit | Description |
|---------|-------------|
| **Lightning Fast** | Opens in under 2 seconds. No splash screens, no waiting. |
| **Zero Configuration** | Connect and query immediately. No complex setup required. |
| **Keyboard-First** | Power users can do everything without touching the mouse. |
| **Beautiful & Modern** | Dark and light themes with a clean, distraction-free UI. |
| **Privacy-First** | No telemetry, no tracking. Your data stays on your machine. |
| **Secure** | Connection credentials are encrypted locally. |
| **Pay Once, Own Forever** | No subscriptions. One-time purchase with 1 year of updates. |
---
## Pricing
### Free Tier
Get started at no cost:
- 2 database connections
- 50 query history items
- 3 editor tabs
- 1 schema for ER diagrams
- CSV/JSON export
### Pro License — ~~$99~~ $29 (Early Bird)
Unlock everything:
- **Unlimited** connections
- **Unlimited** query history
- **Unlimited** tabs
- **Unlimited** ER diagrams
- Inline data editing (INSERT/UPDATE/DELETE)
- Query execution plans (EXPLAIN/ANALYZE)
- 3 device activations
- 1 year of updates
- **Pay once, use forever**
### Cloud (Coming Soon)
For power users and teams:
- Everything in Pro
- Sync connections across devices
- Cloud-saved queries
- Team sharing
- ~$5-8/month
---
## Feature List
### Connection Management
| Feature | Description |
|---------|-------------|
| **Quick Connection Setup** | Add connections with host, port, database, user, and password — or paste a connection string |
| **Connection String Parsing** | Paste any PostgreSQL connection URL and auto-fill all fields |
| **Test Before Save** | Verify connections work before adding them |
| **Encrypted Storage** | Credentials stored securely with encryption |
| **SSL Support** | Connect to SSL-enabled databases |
| **Connection Switcher** | Quickly switch between multiple database connections |
| **Edit & Delete** | Manage saved connections with ease |
### Query Editor
| Feature | Description |
|---------|-------------|
| **Monaco Editor** | Same editor engine that powers VS Code |
| **SQL Syntax Highlighting** | Full SQL syntax highlighting with PostgreSQL support |
| **Smart Autocomplete** | Schema-aware suggestions for tables, columns, and SQL keywords |
| **Multi-Tab Support** | Work on multiple queries simultaneously with independent tabs |
| **Query Formatting** | Auto-format SQL with `Cmd/Ctrl + Shift + F` |
| **Run Query** | Execute with `Cmd/Ctrl + Enter` |
| **Collapsible Editor** | Minimize the editor to focus on results |
### Results Viewer
| Feature | Description |
|---------|-------------|
| **Data Table View** | View results in a clean, sortable table |
| **Data Type Indicators** | Color-coded badges showing column types |
| **Query Metrics** | See row count and query execution time |
| **Pagination** | Navigate large result sets with customizable page sizes |
| **Copy Cell** | Click any cell to copy its value |
| **Copy Row as JSON** | Export individual rows as JSON objects |
| **Export to CSV** | Download results as CSV files |
| **Export to JSON** | Download results as JSON files |
| **NULL Styling** | Clear visual distinction for NULL values |
| **Foreign Key Navigation** | Click FK cells to view related records |
| **JSON/JSONB Viewer** | Expand and inspect JSON columns inline |
### Schema Explorer
| Feature | Description |
|---------|-------------|
| **Tree View Navigation** | Browse schemas, tables, and views hierarchically |
| **Column Details** | See column names, data types, and constraints |
| **Primary Key Indicators** | Visual markers for primary key columns |
| **Nullable Indicators** | See which columns allow NULL values |
| **Foreign Key Display** | View foreign key relationships |
| **Table Search** | Filter tables by name with instant search |
| **Click to Query** | Click any table to generate a SELECT query |
| **Schema Refresh** | Reload schema after database changes |
### Query History
| Feature | Description |
|---------|-------------|
| **Auto-Save** | Every executed query is automatically saved |
| **Persistent Storage** | History survives app restarts |
| **Query Metadata** | See execution time, row count, and status for each query |
| **Quick Load** | Click any history item to load it into the editor |
| **Copy to Clipboard** | Copy previous queries without loading |
| **Clear History** | Remove all or individual history items |
| **Query Type Badges** | Visual indicators for SELECT, INSERT, UPDATE, DELETE |
| **Relative Timestamps** | "5 minutes ago", "yesterday", etc. |
### Inline Data Editing
| Feature | Description |
|---------|-------------|
| **Edit Cells** | Double-click to modify cell values directly |
| **Add Rows** | Insert new records with a visual form |
| **Delete Rows** | Remove records with confirmation |
| **SQL Preview** | Review generated SQL before executing changes |
| **Batch Operations** | Queue multiple changes before committing |
| **Discard Changes** | Undo pending edits before saving |
| **Type-Safe Editing** | Input validation based on column data types |
### ER Diagram Visualization
| Feature | Description |
|---------|-------------|
| **Visual Schema Map** | See your database structure as an interactive diagram |
| **Table Nodes** | Each table displays all columns with types |
| **Relationship Lines** | Foreign key connections visualized as links |
| **Primary Key Highlights** | Yellow indicators for PK columns |
| **Foreign Key Highlights** | Blue indicators for FK columns |
| **Pan & Zoom** | Navigate large schemas with ease |
| **Mini Map** | Overview navigation for complex databases |
### Query Execution Plans
| Feature | Description |
|---------|-------------|
| **EXPLAIN Visualization** | See query execution plans in a visual tree |
| **Node Type Coloring** | Color-coded operations (scans, joins, sorts) |
| **Cost Analysis** | View estimated vs actual costs |
| **Performance Metrics** | Execution time breakdown by operation |
| **Buffer Statistics** | I/O and memory usage details |
| **Expandable Nodes** | Drill into plan details |
### User Interface
| Feature | Description |
|---------|-------------|
| **Dark Mode** | Easy on the eyes for long coding sessions |
| **Light Mode** | Clean, bright interface when you prefer it |
| **System Preference** | Automatically match your OS theme |
| **Resizable Panels** | Drag to resize sidebar and editor |
| **Collapsible Sidebar** | Maximize workspace when needed |
| **Loading States** | Clear feedback during operations |
| **Error Handling** | Helpful error messages with details |
| **Empty States** | Guided prompts when there's no data |
### Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| `Cmd/Ctrl + Enter` | Execute query |
| `Cmd/Ctrl + Shift + F` | Format SQL |
| `Cmd/Ctrl + P` | Open connection picker |
| `Cmd/Ctrl + S` | Save query to file |
| `Cmd/Ctrl + O` | Open query from file |
| `Cmd/Ctrl + Shift + 1-9` | Switch between connections |
---
## Technical Highlights
| Aspect | Details |
|--------|---------|
| **Framework** | Electron with React 19 |
| **Editor** | Monaco (VS Code engine) |
| **Database Driver** | Native PostgreSQL (pg) |
| **Local Storage** | SQLite for history and settings |
| **Security** | Encrypted credential storage |
| **Build Targets** | macOS DMG, Windows exe/msi, Linux AppImage |
---
## What data-peek is NOT
To set clear expectations:
- **Not a database admin tool** — Focus is on querying and exploring, not server management
- **Not a data migration tool** — No import/export of entire databases
- **Not multi-database** — PostgreSQL only (MySQL/SQLite coming in future versions)
- **Not enterprise software** — Built for individual developers (team features coming with Cloud tier)
---
## Comparison with Alternatives
| Feature | data-peek | pgAdmin | DBeaver | TablePlus |
|---------|-----------|---------|---------|-----------|
| Startup Time | < 2s | 5-10s | 10-15s | 2-3s |
| Memory Usage | Low | High | Very High | Low |
| Learning Curve | Minimal | Steep | Steep | Minimal |
| Price | Free + $29 Pro | Free | Free/Paid | $69 |
| PostgreSQL Focus | Yes | Yes | No | No |
| ER Diagrams | Yes | Yes | Yes | Yes |
| Inline Editing | Yes | Yes | Yes | Yes |
| Query Plans | Yes | Yes | Yes | Limited |
| Modern UI | Yes | No | No | Yes |
---
## Coming Soon
Features planned for future releases:
- MySQL and SQLite support
- SSH tunnel connections
- Saved queries / snippets library
- Query cancellation
- CSV data import
- Connection groups/folders
- **Cloud Sync** — Sync connections and saved queries across devices
- **Team Features** — Share queries and connections with your team
---
## Screenshots
*[Add screenshots here]*
- Connection dialog
- Query editor with results
- Schema explorer tree
- ER diagram view
- Query execution plan
- Inline data editing
- Dark/Light theme comparison
---
## One-Liner Descriptions
For various marketing contexts:
**Tagline:**
> Peek at your data. Fast.
**Short (10 words):**
> A fast, beautiful PostgreSQL client for developers who value simplicity.
**Medium (25 words):**
> data-peek is a lightweight PostgreSQL desktop client with a modern UI, keyboard shortcuts, and features like ER diagrams and query plans — without the bloat.
**Long (50 words):**
> data-peek is the PostgreSQL client developers actually want to use. Lightning-fast startup, Monaco-powered SQL editor, visual ER diagrams, query execution plans, inline data editing, and a beautiful dark/light UI. No telemetry, no subscriptions, no bloat. Pay once, own forever. Available for macOS, Windows, and Linux.
---
*Document generated: November 2025*

View file

@ -1,20 +0,0 @@
# Future Plan
## Quick Wins
- [x] Query history - browse and re-run previous queries
- [x] Export results to CSV/JSON
- [x] Table search - quickly find tables in large schemas
- [x] Dark/light theme toggle (already in settings)
- [x] Connection picker (Cmd+P) and quick switching (Cmd+Shift+1-9)
## Medium Effort
- [ ] Saved queries/snippets - bookmark frequently used queries
- [ ] Column statistics - show min/max/null counts/distinct values
- [x] Connection folders/groups - organize many connections
- [ ] Multi-statement query execution (run multiple queries separated by `;`)
## Bigger Features
- [x] ERD visualization - see table relationships visually
- [x] Query execution plan viewer - analyze query performance (EXPLAIN ANALYZE)
- [ ] Data visualization - charts from query results
- [x] SQL autocomplete improvements (table/column suggestions) - already implemented

View file

@ -1,329 +0,0 @@
# data-peek Release Guide
This document covers everything needed to ship data-peek for macOS, Windows, and Linux.
---
## TL;DR — Can I Ship for Free?
**Yes.** Here's the reality:
| Platform | Free Option | Paid Option | User Experience Difference |
|----------|-------------|-------------|---------------------------|
| **Linux** | ✅ No signing needed | N/A | No difference |
| **macOS** | ✅ Ad-hoc signing | $99/year Apple Developer | Warning dialog vs no warning |
| **Windows** | ✅ Unsigned | ~$200-400/year certificate | SmartScreen warning vs trusted |
Most open-source Electron apps ship unsigned. Users can bypass warnings.
---
## Platform-Specific Details
### Linux — No Signing Required ✅
Linux doesn't require code signing. Your current setup is ready:
- AppImage: Works out of the box
- Snap: Works out of the box
- Deb: Works out of the box
**Action needed:** None. Just build and distribute.
---
### macOS — Options
#### Option 1: Unsigned/Ad-hoc (Free) ✅
Users will see: *"data-peek can't be opened because Apple cannot check it for malicious software"*
**Workaround for users:**
1. Right-click the app → Open (first time only)
2. Or: `xattrs -cr /Applications/data-peek.app`
**Pros:** Free, works fine for developer tools
**Cons:** Scary warning, extra step for users
**Current config already supports this** — just build and distribute.
#### Option 2: Apple Developer Program ($99/year)
Users will see: No warning, app opens normally.
**What you get:**
- Developer ID certificate for signing
- Notarization (Apple scans your app)
- No Gatekeeper warnings
**When to consider:** If you want a polished user experience or plan to distribute via Mac App Store.
---
### Windows — Options
#### Option 1: Unsigned (Free) ✅
Users will see: *"Windows protected your PC — Microsoft Defender SmartScreen prevented an unrecognized app from starting"*
**Workaround for users:**
1. Click "More info"
2. Click "Run anyway"
**Pros:** Free
**Cons:** SmartScreen warning scares users, some corporate machines block unsigned apps
**Note:** SmartScreen builds reputation over time. After enough users download and run your app without issues, warnings may reduce.
#### Option 2: Code Signing Certificate ($200-600/year)
Traditional EV (Extended Validation) certificates from:
- DigiCert (~$400-600/year)
- Sectigo (~$200-400/year)
- SSL.com (~$200/year)
**Pros:** Immediate SmartScreen trust (EV certs), professional appearance
**Cons:** Expensive, annual renewal
#### Option 3: Azure Trusted Signing (~$10/month) 💡
Microsoft's newer, cheaper alternative:
- Pay-as-you-go pricing
- Works with electron-builder
- Builds SmartScreen reputation faster than unsigned
**Pros:** Cheap, modern, Microsoft-backed
**Cons:** Requires Azure account, slightly more setup
#### Option 4: SignPath (Free for Open Source) 💡
[SignPath.io](https://signpath.io) offers free code signing for open-source projects.
**Requirements:**
- Public GitHub repository
- OSS license (MIT qualifies)
- Apply and get approved
**Pros:** Completely free
**Cons:** Approval process, builds must go through their CI
---
## Recommended Approach
### For Initial Release (v1.0)
Ship unsigned on all platforms:
1. **Linux** — No changes needed
2. **macOS** — Users right-click → Open (one time)
3. **Windows** — Users click "More info" → "Run anyway"
Include clear installation instructions in README.
### For Future Releases
Consider signing when:
- You have paying users (Pro tier)
- Download volume justifies the cost
- Corporate users need signed apps
---
## Release Checklist
### Pre-Release
- [ ] Update `electron-builder.yml`:
- [ ] Change `appId` to `com.datapeek.app`
- [ ] Remove `electronDownload.mirror` line
- [ ] Update `publish` config (see below)
- [ ] Update `package.json`:
- [ ] Set proper `author`
- [ ] Set `homepage` to GitHub repo
- [ ] Add LICENSE file (MIT)
- [ ] Add README with screenshots and install instructions
- [ ] Test builds on each platform
### electron-builder.yml Updates
```yaml
appId: com.datapeek.app
productName: data-peek
# ... keep existing config ...
# For GitHub Releases (recommended)
publish:
provider: github
owner: Rohithgilla12
repo: data-peek
# Remove this line:
# electronDownload:
# mirror: https://npmmirror.com/mirrors/electron/
```
### Building Releases
```bash
# From apps/desktop directory
# macOS (builds for current architecture)
pnpm build:mac
# macOS universal (Intel + Apple Silicon)
# Add to electron-builder.yml under mac:
# target:
# - target: dmg
# arch: [x64, arm64]
# Windows
pnpm build:win
# Linux
pnpm build:linux
```
### Creating a GitHub Release
1. Tag your release:
```bash
git tag v1.0.0
git push origin v1.0.0
```
2. Go to GitHub → Releases → Draft new release
3. Upload build artifacts:
- `data-peek-desktop-1.0.0.dmg` (macOS)
- `data-peek-desktop-1.0.0-setup.exe` (Windows)
- `data-peek-desktop-1.0.0.AppImage` (Linux)
- `data-peek-desktop-1.0.0.snap` (Linux)
- `data-peek-desktop-1.0.0.deb` (Linux)
4. Write release notes and publish
---
## CI/CD (Optional but Recommended)
Automate builds with GitHub Actions. Create `.github/workflows/release.yml`:
```yaml
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- name: Build
run: pnpm --filter @data-peek/desktop build
- name: Build Electron app
run: |
cd apps/desktop
pnpm exec electron-builder --publish never
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.os }}
path: apps/desktop/dist/*.{dmg,exe,AppImage,snap,deb}
```
---
## Installation Instructions for Users
Include this in your README:
### macOS
1. Download `data-peek-desktop-x.x.x.dmg`
2. Open the DMG and drag to Applications
3. **First launch:** Right-click the app → Click "Open" → Click "Open" again
(This is required because the app is not notarized)
### Windows
1. Download `data-peek-desktop-x.x.x-setup.exe`
2. Run the installer
3. **If you see SmartScreen warning:** Click "More info" → "Run anyway"
### Linux
**AppImage:**
```bash
chmod +x data-peek-desktop-x.x.x.AppImage
./data-peek-desktop-x.x.x.AppImage
```
**Snap:**
```bash
sudo snap install data-peek-desktop-x.x.x.snap --dangerous
```
**Deb:**
```bash
sudo dpkg -i data-peek-desktop-x.x.x.deb
```
---
## Cost Summary
| Approach | Cost | Notes |
|----------|------|-------|
| Ship unsigned | $0 | Users see warnings |
| macOS only signing | $99/year | Apple Developer Program |
| Windows only signing | $10-400/year | Azure TS or traditional cert |
| Both platforms signed | $109-500/year | Combined |
| SignPath (Windows) | $0 | Free for OSS, requires approval |
---
## Future Considerations
### Auto-Updates
Current config uses `electron-updater`. For unsigned apps:
- macOS: Auto-updates work but user must approve
- Windows: Auto-updates work
- Linux: AppImage supports auto-updates via `electron-updater`
### Mac App Store
Requires Apple Developer Program + additional review process. Consider only if:
- You want discoverability
- You're okay with Apple's 15-30% cut
- Your app meets their guidelines
### Windows Store
Possible but requires Microsoft Partner account. Similar considerations to Mac App Store.

View file

@ -1,333 +0,0 @@
# data-peek v1.0 Scope Document
> A minimal, fast, beautiful Postgres client for developers who want to quickly peek at their data.
## Target User
Developers who need a lightweight alternative to pgAdmin/DBeaver for day-to-day queries.
## Core Principles
- **Simple over feature-rich** — Do less, but do it well
- **Fast to open, fast to query** — No bloat
- **Keyboard-first** — Power users shouldn't need a mouse
---
## Tech Stack
| Layer | Choice | Why |
|-------|--------|-----|
| Desktop | Electron | Community, TS-native, contributor-friendly |
| Frontend | React + TypeScript | Industry standard, type safety |
| Bundler | electron-vite | Fast, modern, great DX |
| UI | shadcn/ui + Tailwind | Beautiful, accessible, customizable |
| State | Zustand | Simple, minimal boilerplate |
| Query Editor | Monaco | VS Code engine, excellent SQL support |
| Local Storage | SQLite (better-sqlite3) | Query history, cached schemas |
| Config | electron-store | Encrypted connection credentials |
| Database Client | pg | Native Postgres driver |
---
## In Scope (v1.0)
### Connection Management
- [x] Add new Postgres connection (host, port, database, user, password)
- [x] Edit existing connection
- [x] Delete connection
- [x] Test connection before saving
- [x] Encrypted credential storage (electron-store)
- [x] Connection list in sidebar
- [x] SSL connection support (basic)
### Query Editor
- [x] Monaco editor with SQL syntax highlighting
- [x] Single query tab (multi-tab is v1.1) — *Actually implemented multi-tab!*
- [x] Run query: `Cmd/Ctrl + Enter`
- [ ] Clear editor: `Cmd/Ctrl + L`
- [x] Basic error display with message
### Results Viewer
- [x] Table view with columns and rows
- [x] Column headers with data type indicators
- [x] Row count and query duration display
- [x] Client-side pagination (100 rows per page)
- [x] Copy cell value on click
- [x] Copy row as JSON
- [x] Export results to CSV
- [x] Export results to JSON
- [x] NULL value indicator styling
### Schema Explorer
- [x] Tree view: Connection → Schemas → Tables/Views
- [x] Show columns under each table:
- Column name
- Data type
- Nullable indicator
- Primary key indicator
- [x] Click table name to insert into editor
- [x] Refresh schema button
- [x] Collapse/expand nodes
### Query History
- [x] Auto-save last 100 executed queries (local SQLite)
- [x] Store: query text, timestamp, duration, row count
- [x] Display in sidebar panel
- [x] Click to load query into editor
- [x] Clear history option
- [x] Persist across sessions
### UI/UX
- [x] Dark mode only (light mode is v1.1) — *Actually implemented light mode + system preference!*
- [x] Resizable sidebar (drag handle)
- [x] Loading states for queries
- [x] Empty states with helpful messages
- [x] Error states with clear messaging
- [x] Keyboard shortcuts:
- `Cmd/Ctrl + Enter` — Run query
- `Cmd/Ctrl + S` — Save query to file
- `Cmd/Ctrl + O` — Open query from file
- `Cmd/Ctrl + ,` — Open settings
### Platform Support
- [x] macOS build (DMG, Apple Silicon + Intel)
- [x] Linux build (AppImage)
- [x] Windows build (exe/msi)
---
## Out of Scope (v1.0)
These features are explicitly deferred to future versions. When tempted to add them, resist.
| Feature | Target Version | Status |
|---------|----------------|--------|
| Multiple query tabs | v1.1 | ✅ Done |
| MySQL adapter | v1.1 | |
| SQLite adapter | v1.1 | |
| Light theme | v1.1 | ✅ Done |
| Connection groups/folders | v1.1 | |
| Autocomplete (tables/columns) | v1.2 | ✅ Done (schema-aware) |
| Query formatting/beautify | v1.2 | ✅ Done |
| Saved queries / snippets library | v1.2 | |
| SSH tunnel connections | v1.2 | |
| Query cancellation | v1.2 | |
| Table data editing (inline UPDATE/INSERT) | v2.0 | ✅ Done |
| ER diagram visualization | v2.0 | ✅ Done |
| Query EXPLAIN/ANALYZE visualizer | v2.0 | ✅ Done |
| Import data from CSV | v2.0 | |
| Database diff tool | v2.0 | |
| Cloud sync (connections, history) | Pro | |
| Team workspaces | Pro | |
| Shared query library | Pro | |
| SSO / SAML | Pro | |
| Audit logs | Pro | |
---
## Technical Boundaries
- **No ORM** — Raw `pg` client only, keep it simple
- **No server component** — Pure desktop app, no backend
- **No auth in v1** — Local app, no user accounts
- **No auto-updates in v1** — Manual download for updates
- **No telemetry** — Privacy first, no tracking
---
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────┐
│ Renderer Process (React) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐│
│ │ Sidebar │ │ Query Editor│ │ Results Viewer ││
│ │ - Conns │ │ (Monaco) │ │ - Table/JSON/Export ││
│ │ - Schema │ │ │ │ ││
│ │ - History │ │ │ │ ││
│ └─────────────┘ └─────────────┘ └─────────────────────────┘│
│ │ │
│ ┌───────▼───────┐ │
│ │ IPC Bridge │ │
│ └───────┬───────┘ │
└────────────────────────────┼────────────────────────────────┘
┌────────────────────────────┼────────────────────────────────┐
│ Main Process (Node.js) │
│ ┌───────▼───────┐ │
│ │ Service Layer │ │
│ └───────┬───────┘ │
│ ┌───────────────────┼───────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Connection│ │ Query │ │ Schema │ │
│ │ Manager │ │ Engine │ │ Explorer │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ └──────────────────┼───────────────────┘ │
│ ┌─────▼─────┐ │
│ │ Postgres │ │
│ │ Adapter │ │
│ └───────────┘ │
└─────────────────────────────────────────────────────────────┘
```
---
## UI Layout
```
┌──────────────────────────────────────────────────────────────────┐
│ data-peek [Connection: prod-db ▼] [⚙️] │
├────────────────┬─────────────────────────────────────────────────┤
│ │ │
│ CONNECTIONS │ SELECT * FROM users │
│ ├─ prod-db ● │ WHERE created_at > '2024-01-01' │
│ ├─ staging │ LIMIT 100; │
│ └─ local │ │
│ │ [▶ Run] │
│ SCHEMA ├─────────────────────────────────────────────────┤
│ ▼ public │ │
│ ├─ users │ id │ name │ email │ created_at │
│ ├─ orders │ ───┼─────────┼─────────────────┼────────────── │
│ └─ products │ 1 │ Alice │ alice@test.com │ 2024-01-15 │
│ │ 2 │ Bob │ bob@test.com │ 2024-02-20 │
│ HISTORY │ 3 │ Carol │ carol@test.com │ 2024-03-10 │
│ ├─ SELECT ... │ ... │
│ ├─ UPDATE ... ├─────────────────────────────────────────────────┤
│ └─ ... │ ✓ 100 rows │ 24ms │ Page 1/10 │ [CSV] [JSON] │
└────────────────┴─────────────────────────────────────────────────┘
```
---
## Milestones
### M1: Foundation
- [x] Electron app scaffolded with electron-vite
- [x] Monorepo structure with pnpm workspaces
- [x] Can connect to a Postgres database
- [x] Can run a hardcoded query and log results
### M2: Connection Management
- [x] Connection form UI (add/edit)
- [x] Connection list in sidebar
- [x] Test connection functionality
- [x] Encrypted storage with electron-store
- [x] Delete connection
### M3: Schema Explorer
- [x] Fetch and display schemas
- [x] Fetch and display tables per schema
- [x] Fetch and display columns per table
- [x] Tree view component with expand/collapse
- [x] Click to insert table name
### M4: Query Editor
- [x] Monaco editor integration
- [x] SQL syntax highlighting
- [x] Run query button
- [x] Keyboard shortcut (Cmd+Enter)
- [x] Error display
### M5: Results Viewer
- [x] Table component for results
- [x] Column headers
- [x] Pagination
- [x] Copy cell/row
- [x] Export to CSV
- [x] Export to JSON
### M6: Query History
- [x] SQLite setup for local storage
- [x] Auto-save executed queries
- [x] History list in sidebar
- [x] Click to load into editor
- [x] Clear history
### M7: Polish & Release
- [x] Keyboard shortcuts complete
- [x] Loading states
- [x] Error handling
- [x] Empty states
- [x] Build for macOS
- [x] Build for Linux
- [x] Build for Windows
- [ ] README with screenshots
- [ ] v1.0 release
---
## Success Criteria
v1.0 is complete when:
1. ✅ Can connect to a Postgres database
2. ✅ Can browse schema (schemas, tables, columns)
3. ✅ Can write and run SQL queries
4. ✅ Can view results in a table
5. ✅ Can export results to CSV/JSON
6. ✅ Query history persists across sessions
7. ✅ App opens in under 2 seconds
8. ✅ Feels snappy — no UI lag
9. ✅ Builds available for macOS, Linux, Windows
---
## Non-Goals
To keep scope tight, these are explicitly NOT goals for v1:
- Being a full database administration tool
- Competing with DataGrip/TablePlus on features
- Supporting every Postgres feature
- Having a plugin system
- Mobile support
---
## Open Source Strategy
### License
MIT — Maximum adoption, contributor-friendly.
### Contribution Guidelines
- PRs welcome for bug fixes and v1 scope items
- Features outside v1 scope will be reviewed for v1.1+
- All PRs require tests for new functionality
### Future Monetization (Post v1)
Open core model:
- Free: Everything in v1 scope, forever
- Pro: Cloud sync, team features, priority support
---
## Resources
- [Electron Documentation](https://www.electronjs.org/docs)
- [electron-vite](https://electron-vite.org/)
- [Monaco Editor React](https://github.com/suren-atoyan/monaco-react)
- [shadcn/ui](https://ui.shadcn.com/)
- [Zustand](https://github.com/pmndrs/zustand)
- [node-postgres (pg)](https://node-postgres.com/)
---
## Changelog
| Date | Change |
|------|--------|
| 2024-XX-XX | Initial scope document created |
| 2025-11-28 | Updated implementation status - v1.0 scope complete + bonus v2.0 features |
---
*Remember: When in doubt, leave it out. Ship v1, then iterate.*

View file

@ -1,831 +0,0 @@
# data-peek Web Integration Guide
> Complete walkthrough for testing, purchasing, and integrating the license system with the desktop app.
---
## Table of Contents
1. [Feature Checklist](#feature-checklist)
2. [Environment Setup](#environment-setup)
3. [Purchase Flow](#purchase-flow)
4. [Desktop App Integration](#desktop-app-integration)
5. [API Reference](#api-reference)
6. [Testing Scenarios](#testing-scenarios)
---
## Feature Checklist
### Marketing Site
| Page | Feature | Status |
|------|---------|--------|
| **Landing** | Hero section with animations | ⬜ |
| **Landing** | Features grid (12 cards) | ⬜ |
| **Landing** | Pricing cards (Free vs Pro) | ⬜ |
| **Landing** | Comparison table | ⬜ |
| **Landing** | FAQ accordion | ⬜ |
| **Landing** | CTA section | ⬜ |
| **Landing** | Footer with links | ⬜ |
| **Landing** | Mobile responsive | ⬜ |
| **Download** | Platform cards (macOS, Windows, Linux) | ⬜ |
| **Download** | Download links work | ⬜ |
| **Download** | System requirements shown | ⬜ |
### Screenshots to Add
Replace these placeholder locations with actual screenshots:
| Location | File | Recommended Size |
|----------|------|------------------|
| Hero section | `public/screenshots/hero.png` | 1920×1080 |
| Query Editor | `public/screenshots/editor.png` | 1200×750 |
| ER Diagrams | `public/screenshots/erd.png` | 1200×750 |
### Backend APIs
| Endpoint | Test Command | Expected |
|----------|--------------|----------|
| License Validate | `curl -X POST /api/license/validate` | Returns validation status |
| License Activate | `curl -X POST /api/license/activate` | Creates activation |
| License Deactivate | `curl -X POST /api/license/deactivate` | Removes activation |
| Update Check | `curl /api/updates/check?version=1.0.0` | Returns update info |
| Dodo Webhook | POST with signature | Creates license |
---
## Environment Setup
### 1. Database (Supabase or Neon)
```bash
# Create a PostgreSQL database, then:
cd apps/web
cp .env.example .env.local
```
Add your database URL:
```env
DATABASE_URL="postgresql://user:password@host:5432/database?sslmode=require"
```
Run migrations:
```bash
pnpm db:push
```
### 2. Clerk Authentication
1. Create account at [clerk.com](https://clerk.com)
2. Create a new application
3. Copy keys to `.env.local`:
```env
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_..."
CLERK_SECRET_KEY="sk_test_..."
```
### 3. DodoPayments
1. Create account at [dodopayments.com](https://dodopayments.com)
2. Create a product:
- Name: `data-peek Pro License`
- Type: One-time payment
- Price: $29 (or $99 regular)
3. Set up webhook:
- URL: `https://your-domain.com/api/webhooks/dodo`
- Events: `payment.completed`, `payment.refunded`
4. Copy credentials:
```env
DODO_API_KEY="..."
DODO_WEBHOOK_SECRET="..."
```
### 4. Resend (Email)
1. Create account at [resend.com](https://resend.com)
2. Verify your domain
3. Create API key:
```env
RESEND_API_KEY="re_..."
```
---
## Purchase Flow
### How It Works
```
┌─────────────────────────────────────────────────────────────────┐
│ PURCHASE FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. User clicks "Get Pro — $29" on website │
│ ↓ │
│ 2. Redirected to DodoPayments checkout │
│ ↓ │
│ 3. User completes payment │
│ ↓ │
│ 4. DodoPayments sends webhook to /api/webhooks/dodo │
│ ↓ │
│ 5. Backend creates customer + license in database │
│ ↓ │
│ 6. Welcome email sent with license key │
│ ↓ │
│ 7. User enters key in desktop app → activated! │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### License Key Format
```
DPRO-XXXX-XXXX-XXXX-XXXX
```
- Prefix: `DPRO` (Pro), `DTEAM` (Team), `DENT` (Enterprise)
- 4 groups of 4 alphanumeric characters
- No confusing characters (0, O, 1, I, l excluded)
### Setting Up the Buy Button
Update the pricing component to link to DodoPayments:
```tsx
// src/components/marketing/pricing.tsx
// Replace href with your DodoPayments checkout link
{
cta: 'Get Pro License',
href: 'https://checkout.dodopayments.com/buy/your-product-id',
// Or use their SDK for embedded checkout
}
```
### Testing Purchases Locally
1. Use DodoPayments test mode
2. Use webhook testing tool (ngrok or similar):
```bash
ngrok http 3000
# Use the ngrok URL for webhook endpoint
```
3. Make a test purchase
4. Check database for new license:
```bash
pnpm db:studio
# Opens Drizzle Studio to inspect data
```
---
## Desktop App Integration
### 1. Install Dependencies
```bash
cd apps/desktop
pnpm add node-machine-id
```
### 2. Add License Types
Create `src/shared/license.ts`:
```typescript
export interface LicenseInfo {
valid: boolean
plan: 'free' | 'pro' | 'team' | 'enterprise'
status: 'active' | 'revoked' | 'expired'
updatesUntil: string
activationsUsed: number
activationsMax: number
}
export interface ActivationInfo {
id: string
deviceId: string
deviceName: string | null
activatedAt: string
}
export interface ActivateResponse {
success: boolean
activation?: ActivationInfo
license?: LicenseInfo
error?: string
}
```
### 3. Create License Service (Main Process)
Create `src/main/license.ts`:
```typescript
import { machineIdSync } from 'node-machine-id'
import { app } from 'electron'
import Store from 'electron-store'
import os from 'os'
const store = new Store()
const API_BASE = 'https://datapeek.app/api' // or your domain
// Get unique device identifier
export function getDeviceId(): string {
return machineIdSync()
}
export function getDeviceInfo() {
return {
deviceId: getDeviceId(),
deviceName: os.hostname(),
os: process.platform, // darwin, win32, linux
appVersion: app.getVersion(),
}
}
// Validate license with server
export async function validateLicense(licenseKey: string): Promise<LicenseInfo> {
const response = await fetch(`${API_BASE}/license/validate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
licenseKey,
deviceId: getDeviceId(),
}),
})
return response.json()
}
// Activate license on this device
export async function activateLicense(licenseKey: string): Promise<ActivateResponse> {
const deviceInfo = getDeviceInfo()
const response = await fetch(`${API_BASE}/license/activate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
licenseKey,
...deviceInfo,
}),
})
const result = await response.json()
if (result.success) {
// Store license locally
store.set('license', {
key: licenseKey,
...result.license,
lastValidated: new Date().toISOString(),
})
}
return result
}
// Deactivate this device
export async function deactivateLicense(licenseKey: string): Promise<{ success: boolean }> {
const response = await fetch(`${API_BASE}/license/deactivate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
licenseKey,
deviceId: getDeviceId(),
}),
})
if (response.ok) {
store.delete('license')
}
return response.json()
}
// Get stored license
export function getStoredLicense() {
return store.get('license') as StoredLicense | undefined
}
// Check license on app startup
export async function checkLicenseOnStartup(): Promise<LicenseStatus> {
const stored = getStoredLicense()
if (!stored) {
return { status: 'free', features: FREE_FEATURES }
}
try {
// Try online validation
const result = await validateLicense(stored.key)
if (result.valid) {
// Update cache
store.set('license', {
...stored,
...result,
lastValidated: new Date().toISOString(),
})
return { status: 'pro', features: PRO_FEATURES, license: result }
} else {
// License revoked or invalid
store.delete('license')
return { status: 'free', features: FREE_FEATURES, error: 'License invalid' }
}
} catch (error) {
// Offline - use cached validation with grace period
const lastValidated = new Date(stored.lastValidated)
const gracePeriod = 14 * 24 * 60 * 60 * 1000 // 14 days
if (Date.now() - lastValidated.getTime() < gracePeriod) {
return { status: 'pro', features: PRO_FEATURES, offline: true }
}
return { status: 'free', features: FREE_FEATURES, error: 'License validation failed' }
}
}
```
### 4. Add IPC Handlers
In `src/main/index.ts`:
```typescript
import {
checkLicenseOnStartup,
activateLicense,
deactivateLicense,
getStoredLicense,
} from './license'
// License IPC handlers
ipcMain.handle('license:check', async () => {
return checkLicenseOnStartup()
})
ipcMain.handle('license:activate', async (_, licenseKey: string) => {
return activateLicense(licenseKey)
})
ipcMain.handle('license:deactivate', async (_, licenseKey: string) => {
return deactivateLicense(licenseKey)
})
ipcMain.handle('license:get', async () => {
return getStoredLicense()
})
```
### 5. Update Preload Script
In `src/preload/index.ts`:
```typescript
// Add to the API object
license: {
check: () => ipcRenderer.invoke('license:check'),
activate: (key: string) => ipcRenderer.invoke('license:activate', key),
deactivate: (key: string) => ipcRenderer.invoke('license:deactivate', key),
get: () => ipcRenderer.invoke('license:get'),
}
```
### 6. Create License Store (Renderer)
Create `src/renderer/src/stores/license-store.ts`:
```typescript
import { create } from 'zustand'
interface LicenseState {
status: 'loading' | 'free' | 'pro' | 'team' | 'enterprise'
license: LicenseInfo | null
isOffline: boolean
error: string | null
// Actions
checkLicense: () => Promise<void>
activateLicense: (key: string) => Promise<{ success: boolean; error?: string }>
deactivateLicense: () => Promise<void>
// Feature checks
isPro: () => boolean
canUseFeature: (feature: string) => boolean
}
// Feature limits for free tier
const FREE_LIMITS = {
connections: 2,
queryHistory: 50,
tabs: 3,
erDiagrams: 1,
}
export const useLicenseStore = create<LicenseState>((set, get) => ({
status: 'loading',
license: null,
isOffline: false,
error: null,
checkLicense: async () => {
try {
const result = await window.api.license.check()
set({
status: result.status,
license: result.license ?? null,
isOffline: result.offline ?? false,
error: result.error ?? null,
})
} catch (error) {
set({ status: 'free', error: 'Failed to check license' })
}
},
activateLicense: async (key: string) => {
try {
const result = await window.api.license.activate(key)
if (result.success) {
set({
status: result.license?.plan ?? 'pro',
license: result.license ?? null,
error: null,
})
return { success: true }
}
return { success: false, error: result.error }
} catch (error) {
return { success: false, error: 'Activation failed' }
}
},
deactivateLicense: async () => {
const license = get().license
if (license) {
await window.api.license.deactivate(license.key)
}
set({ status: 'free', license: null })
},
isPro: () => {
const status = get().status
return status === 'pro' || status === 'team' || status === 'enterprise'
},
canUseFeature: (feature: string) => {
const isPro = get().isPro()
if (isPro) return true
// Check free tier limits
switch (feature) {
case 'unlimited-connections':
case 'unlimited-history':
case 'unlimited-tabs':
case 'unlimited-erd':
case 'inline-editing':
case 'query-plans':
return false
default:
return true
}
},
}))
```
### 7. Create License Dialog Component
Create `src/renderer/src/components/license-dialog.tsx`:
```tsx
import { useState } from 'react'
import { useLicenseStore } from '@/stores/license-store'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Key, Check, AlertCircle, ExternalLink } from 'lucide-react'
interface LicenseDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
}
export function LicenseDialog({ open, onOpenChange }: LicenseDialogProps) {
const [licenseKey, setLicenseKey] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const { status, license, activateLicense, deactivateLicense } = useLicenseStore()
const isPro = status !== 'free' && status !== 'loading'
const handleActivate = async () => {
if (!licenseKey.trim()) return
setIsLoading(true)
setError(null)
const result = await activateLicense(licenseKey.trim())
setIsLoading(false)
if (result.success) {
setLicenseKey('')
onOpenChange(false)
} else {
setError(result.error ?? 'Activation failed')
}
}
const handleDeactivate = async () => {
setIsLoading(true)
await deactivateLicense()
setIsLoading(false)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Key className="w-5 h-5" />
License
</DialogTitle>
</DialogHeader>
{isPro ? (
// Pro license view
<div className="space-y-4">
<div className="p-4 rounded-lg bg-green-500/10 border border-green-500/20">
<div className="flex items-center gap-2 text-green-500 mb-2">
<Check className="w-4 h-4" />
<span className="font-medium">Pro License Active</span>
</div>
<p className="text-sm text-muted-foreground">
Updates until: {new Date(license?.updatesUntil ?? '').toLocaleDateString()}
</p>
<p className="text-sm text-muted-foreground">
Activations: {license?.activationsUsed} / {license?.activationsMax}
</p>
</div>
<Button
variant="outline"
className="w-full"
onClick={handleDeactivate}
disabled={isLoading}
>
Deactivate This Device
</Button>
</div>
) : (
// Free tier view
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium">License Key</label>
<Input
placeholder="DPRO-XXXX-XXXX-XXXX-XXXX"
value={licenseKey}
onChange={(e) => setLicenseKey(e.target.value.toUpperCase())}
className="font-mono"
/>
</div>
{error && (
<div className="flex items-center gap-2 text-red-500 text-sm">
<AlertCircle className="w-4 h-4" />
{error}
</div>
)}
<Button
className="w-full"
onClick={handleActivate}
disabled={isLoading || !licenseKey.trim()}
>
{isLoading ? 'Activating...' : 'Activate License'}
</Button>
<div className="text-center">
<a
href="https://datapeek.app/#pricing"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-primary hover:underline inline-flex items-center gap-1"
>
Get a Pro license
<ExternalLink className="w-3 h-3" />
</a>
</div>
</div>
)}
</DialogContent>
</Dialog>
)
}
```
### 8. Feature Gating Example
```tsx
// Example: Gating the "Add Connection" button
import { useLicenseStore } from '@/stores/license-store'
function ConnectionList() {
const { isPro, canUseFeature } = useLicenseStore()
const connections = useConnectionStore((s) => s.connections)
const canAddConnection = isPro() || connections.length < 2
return (
<div>
{/* ... connection list ... */}
<Button
onClick={handleAddConnection}
disabled={!canAddConnection}
>
Add Connection
{!canAddConnection && (
<Badge variant="secondary" className="ml-2">Pro</Badge>
)}
</Button>
{!canAddConnection && (
<p className="text-xs text-muted-foreground mt-2">
Free tier limited to 2 connections.
<a href="#" onClick={openLicenseDialog}>Upgrade to Pro</a>
</p>
)}
</div>
)
}
```
---
## API Reference
### POST /api/license/validate
Validate a license key and check if device is activated.
**Request:**
```json
{
"licenseKey": "DPRO-XXXX-XXXX-XXXX-XXXX",
"deviceId": "unique-machine-id"
}
```
**Response:**
```json
{
"valid": true,
"plan": "pro",
"status": "active",
"updatesUntil": "2025-11-28T00:00:00.000Z",
"activationsUsed": 1,
"activationsMax": 3
}
```
### POST /api/license/activate
Activate a license on a new device.
**Request:**
```json
{
"licenseKey": "DPRO-XXXX-XXXX-XXXX-XXXX",
"deviceId": "unique-machine-id",
"deviceName": "MacBook Pro",
"os": "darwin",
"appVersion": "1.0.0"
}
```
**Response:**
```json
{
"success": true,
"activation": {
"id": "uuid",
"deviceId": "unique-machine-id",
"deviceName": "MacBook Pro",
"activatedAt": "2024-11-28T00:00:00.000Z"
},
"license": {
"plan": "pro",
"updatesUntil": "2025-11-28T00:00:00.000Z",
"activationsUsed": 1,
"activationsMax": 3
}
}
```
### POST /api/license/deactivate
Deactivate a device.
**Request:**
```json
{
"licenseKey": "DPRO-XXXX-XXXX-XXXX-XXXX",
"deviceId": "unique-machine-id"
}
```
**Response:**
```json
{
"success": true,
"activationsRemaining": 2
}
```
### GET /api/updates/check
Check for app updates.
**Request:**
```
GET /api/updates/check?version=1.0.0&platform=macos-arm
```
**Response:**
```json
{
"hasUpdate": true,
"latestVersion": "1.1.0",
"currentVersion": "1.0.0",
"downloadUrl": "https://...",
"releaseNotes": "Bug fixes and improvements",
"forceUpdate": false
}
```
---
## Testing Scenarios
### Manual Test Checklist
| Scenario | Steps | Expected Result |
|----------|-------|-----------------|
| **Fresh install (free)** | Open app with no license | Free tier limits apply |
| **Valid activation** | Enter valid license key | Unlocks Pro features |
| **Invalid key** | Enter random key | Shows error message |
| **Max activations** | Activate on 4th device | Shows "max reached" error |
| **Deactivate** | Deactivate from settings | Reverts to free tier |
| **Offline mode** | Disconnect internet, open app | Uses cached license (14 day grace) |
| **Revoked license** | Revoke via webhook | Shows "revoked" error on next validation |
| **Update check** | Use older version | Shows update available |
### Test License Keys
For development, you can manually insert test licenses:
```sql
-- Insert test customer
INSERT INTO customers (email, name)
VALUES ('test@example.com', 'Test User');
-- Insert test license (get customer ID from above)
INSERT INTO licenses (customer_id, license_key, plan, status, max_activations, updates_until)
VALUES (
'customer-uuid-here',
'DPRO-TEST-TEST-TEST-TEST',
'pro',
'active',
3,
NOW() + INTERVAL '1 year'
);
```
---
## Deployment Checklist
- [ ] Database migrations run on production
- [ ] Environment variables set on Vercel/hosting
- [ ] DodoPayments webhook URL updated to production
- [ ] Clerk production keys configured
- [ ] Resend domain verified
- [ ] Download links point to actual releases
- [ ] Screenshots added to marketing site
---
*Document created: November 2024*