Skip to content

Commit 60e0eba

Browse files
nullcoderClaude
andauthored
feat: create core TypeScript interfaces and types (#31)
- Add GistMetadata interface with system fields and edit authentication - Add UserMetadata and FileMetadata for encrypted content - Add EncryptedData structure for secure data storage - Create comprehensive API request/response interfaces - Implement error types with standardized error codes - Define binary format types for efficient file encoding - Add Cloudflare Workers environment type definitions - Export all types from central index file All interfaces are fully documented with JSDoc comments and follow the data models defined in docs/SPEC.md. Types are edge-runtime compatible and ready for use across the application. Closes #26 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude <claude@ghostpaste.dev>
1 parent 403aea3 commit 60e0eba

File tree

6 files changed

+437
-0
lines changed

6 files changed

+437
-0
lines changed

types/api.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* API request and response types for GhostPaste
3+
*/
4+
5+
import { File, GistOptions, GistMetadata } from "./models";
6+
7+
/**
8+
* Request body for creating a new gist
9+
*/
10+
export interface CreateGistRequest {
11+
files: File[];
12+
options?: GistOptions;
13+
}
14+
15+
/**
16+
* Response from creating a new gist
17+
*/
18+
export interface CreateGistResponse {
19+
id: string;
20+
url: string;
21+
expires_at?: string;
22+
}
23+
24+
/**
25+
* Response from getting gist metadata
26+
*/
27+
export interface GetGistResponse {
28+
metadata: GistMetadata;
29+
decryption_key?: string; // Only included if requested via fragment
30+
}
31+
32+
/**
33+
* Response from getting an encrypted blob
34+
*/
35+
export interface GetBlobResponse {
36+
encrypted_data: string; // Base64 encoded encrypted blob
37+
iv: string; // Base64 encoded initialization vector
38+
}
39+
40+
/**
41+
* Request body for updating a gist
42+
*/
43+
export interface UpdateGistRequest {
44+
files: File[];
45+
options?: Partial<GistOptions>;
46+
edit_pin: string; // Required for authentication
47+
}
48+
49+
/**
50+
* Response from updating a gist
51+
*/
52+
export interface UpdateGistResponse {
53+
success: boolean;
54+
updated_at: string;
55+
}
56+
57+
/**
58+
* Request headers for PIN authentication
59+
*/
60+
export interface AuthHeaders {
61+
"X-Edit-Pin": string;
62+
}
63+
64+
/**
65+
* Query parameters for API endpoints
66+
*/
67+
export interface GistQueryParams {
68+
include_key?: boolean; // Include decryption key in response
69+
}

types/binary.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Binary format types for efficient file encoding
3+
*/
4+
5+
/**
6+
* Binary format version for future compatibility
7+
*/
8+
export const BINARY_FORMAT_VERSION = 1;
9+
10+
/**
11+
* Magic number to identify binary format (4 bytes: "GPST")
12+
*/
13+
export const MAGIC_NUMBER = 0x47505354; // "GPST" in hex
14+
15+
/**
16+
* Binary format header structure
17+
*/
18+
export interface BinaryHeader {
19+
magic: number; // 4 bytes: Magic number (0x47505354)
20+
version: number; // 1 byte: Format version
21+
fileCount: number; // 2 bytes: Number of files
22+
totalSize: number; // 4 bytes: Total size of all files
23+
}
24+
25+
/**
26+
* File entry in binary format
27+
*/
28+
export interface BinaryFileEntry {
29+
nameLength: number; // 2 bytes: Length of filename
30+
name: string; // Variable: UTF-8 encoded filename
31+
contentLength: number; // 4 bytes: Length of file content
32+
content: Uint8Array; // Variable: File content
33+
languageLength: number; // 1 byte: Length of language string
34+
language?: string; // Variable: Optional language identifier
35+
}
36+
37+
/**
38+
* Complete binary format structure
39+
*/
40+
export interface BinaryFormat {
41+
header: BinaryHeader;
42+
files: BinaryFileEntry[];
43+
}
44+
45+
/**
46+
* Size limits for binary format
47+
*/
48+
export interface BinarySizeLimits {
49+
maxFileSize: number; // 500KB per file
50+
maxTotalSize: number; // 5MB total
51+
maxFileCount: number; // 20 files maximum
52+
maxFilenameLength: number; // 255 characters
53+
maxLanguageLength: number; // 50 characters
54+
}
55+
56+
/**
57+
* Default size limits
58+
*/
59+
export const DEFAULT_SIZE_LIMITS: BinarySizeLimits = {
60+
maxFileSize: 500 * 1024, // 500KB
61+
maxTotalSize: 5 * 1024 * 1024, // 5MB
62+
maxFileCount: 20,
63+
maxFilenameLength: 255,
64+
maxLanguageLength: 50,
65+
};
66+
67+
/**
68+
* Binary encoding/decoding result
69+
*/
70+
export interface BinaryResult<T> {
71+
success: boolean;
72+
data?: T;
73+
error?: string;
74+
}
75+
76+
/**
77+
* Type guards for binary format validation
78+
*/
79+
export function isBinaryHeader(obj: any): obj is BinaryHeader {
80+
return (
81+
typeof obj === "object" &&
82+
obj !== null &&
83+
typeof obj.magic === "number" &&
84+
typeof obj.version === "number" &&
85+
typeof obj.fileCount === "number" &&
86+
typeof obj.totalSize === "number"
87+
);
88+
}
89+
90+
export function isBinaryFileEntry(obj: any): obj is BinaryFileEntry {
91+
return (
92+
typeof obj === "object" &&
93+
obj !== null &&
94+
typeof obj.nameLength === "number" &&
95+
typeof obj.name === "string" &&
96+
typeof obj.contentLength === "number" &&
97+
obj.content instanceof Uint8Array &&
98+
typeof obj.languageLength === "number" &&
99+
(obj.language === undefined || typeof obj.language === "string")
100+
);
101+
}

types/env.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Environment types for Cloudflare Workers
3+
*/
4+
5+
import type {
6+
R2Bucket,
7+
KVNamespace,
8+
AnalyticsEngineDataset,
9+
ExecutionContext,
10+
} from "@cloudflare/workers-types";
11+
12+
/**
13+
* Cloudflare Workers environment bindings
14+
*/
15+
export interface Env {
16+
// R2 bucket binding
17+
GHOSTPASTE_BUCKET: R2Bucket;
18+
19+
// Environment variables
20+
ENVIRONMENT?: "development" | "production";
21+
22+
// Optional rate limiting (Cloudflare Workers KV)
23+
RATE_LIMIT_KV?: KVNamespace;
24+
25+
// Optional analytics (Cloudflare Analytics Engine)
26+
ANALYTICS?: AnalyticsEngineDataset;
27+
}
28+
29+
/**
30+
* Request context for Cloudflare Workers
31+
*/
32+
export interface RequestContext {
33+
env: Env;
34+
ctx: ExecutionContext;
35+
request: Request;
36+
}
37+
38+
/**
39+
* R2 object metadata
40+
*/
41+
export interface R2ObjectMetadata {
42+
key: string;
43+
size: number;
44+
etag: string;
45+
httpEtag: string;
46+
uploaded: Date;
47+
httpMetadata?: Record<string, string>;
48+
customMetadata?: Record<string, string>;
49+
}
50+
51+
/**
52+
* Configuration for R2 operations
53+
*/
54+
export interface R2Config {
55+
gistPrefix: string; // Prefix for gist metadata objects
56+
blobPrefix: string; // Prefix for encrypted blob objects
57+
}
58+
59+
/**
60+
* Default R2 configuration
61+
*/
62+
export const DEFAULT_R2_CONFIG: R2Config = {
63+
gistPrefix: "gists/",
64+
blobPrefix: "blobs/",
65+
};

types/errors.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Error types and error handling for GhostPaste
3+
*/
4+
5+
/**
6+
* Standard error codes for the application
7+
*/
8+
export enum ErrorCode {
9+
// Client errors (4xx)
10+
BAD_REQUEST = "BAD_REQUEST",
11+
UNAUTHORIZED = "UNAUTHORIZED",
12+
FORBIDDEN = "FORBIDDEN",
13+
NOT_FOUND = "NOT_FOUND",
14+
CONFLICT = "CONFLICT",
15+
PAYLOAD_TOO_LARGE = "PAYLOAD_TOO_LARGE",
16+
UNPROCESSABLE_ENTITY = "UNPROCESSABLE_ENTITY",
17+
TOO_MANY_REQUESTS = "TOO_MANY_REQUESTS",
18+
19+
// Server errors (5xx)
20+
INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR",
21+
SERVICE_UNAVAILABLE = "SERVICE_UNAVAILABLE",
22+
23+
// Application-specific errors
24+
INVALID_ENCRYPTION_KEY = "INVALID_ENCRYPTION_KEY",
25+
DECRYPTION_FAILED = "DECRYPTION_FAILED",
26+
INVALID_PIN = "INVALID_PIN",
27+
GIST_EXPIRED = "GIST_EXPIRED",
28+
FILE_TOO_LARGE = "FILE_TOO_LARGE",
29+
TOO_MANY_FILES = "TOO_MANY_FILES",
30+
INVALID_BINARY_FORMAT = "INVALID_BINARY_FORMAT",
31+
STORAGE_ERROR = "STORAGE_ERROR",
32+
}
33+
34+
/**
35+
* Standardized API error response
36+
*/
37+
export interface APIError {
38+
error: {
39+
code: ErrorCode;
40+
message: string;
41+
details?: Record<string, any>;
42+
};
43+
status: number;
44+
}
45+
46+
/**
47+
* Application error class
48+
*/
49+
export class AppError extends Error {
50+
constructor(
51+
public code: ErrorCode,
52+
public statusCode: number,
53+
message: string,
54+
public details?: Record<string, any>
55+
) {
56+
super(message);
57+
this.name = "AppError";
58+
}
59+
60+
toAPIError(): APIError {
61+
return {
62+
error: {
63+
code: this.code,
64+
message: this.message,
65+
details: this.details,
66+
},
67+
status: this.statusCode,
68+
};
69+
}
70+
}
71+
72+
/**
73+
* Error response builder
74+
*/
75+
export function createErrorResponse(
76+
code: ErrorCode,
77+
message: string,
78+
statusCode: number,
79+
details?: Record<string, any>
80+
): APIError {
81+
return {
82+
error: {
83+
code,
84+
message,
85+
details,
86+
},
87+
status: statusCode,
88+
};
89+
}

types/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Central export file for all GhostPaste types
3+
*/
4+
5+
// Core data models
6+
export * from "./models";
7+
8+
// API types
9+
export * from "./api";
10+
11+
// Error types
12+
export * from "./errors";
13+
14+
// Binary format types
15+
export * from "./binary";
16+
17+
// Environment types
18+
export * from "./env";

0 commit comments

Comments
 (0)