diff --git a/api/package.json b/api/package.json index df60186..6f1f750 100644 --- a/api/package.json +++ b/api/package.json @@ -12,6 +12,7 @@ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"" }, "dependencies": { + "@xstreamroll/types": "*", "@nestjs/cache-manager": "^2.3.0", "@nestjs/common": "^10.3.0", "@nestjs/core": "^10.3.0", diff --git a/api/src/admin/admin-stats.service.ts b/api/src/admin/admin-stats.service.ts index 038f794..656a91f 100644 --- a/api/src/admin/admin-stats.service.ts +++ b/api/src/admin/admin-stats.service.ts @@ -1,12 +1,7 @@ import { Injectable } from "@nestjs/common" +import { AdminStats } from "@xstreamroll/types" -export interface AdminStats { - totalUsers: number - totalStreams: number - activeStreams: number - eventsLast24h: number - generatedAt: string -} +export type { AdminStats } /** * Aggregates platform-wide stats for the admin dashboard. diff --git a/api/src/streams/stream.entity.ts b/api/src/streams/stream.entity.ts index e0625bb..4232f54 100644 --- a/api/src/streams/stream.entity.ts +++ b/api/src/streams/stream.entity.ts @@ -3,6 +3,9 @@ * defined in `database/schema.sql`. The controller and service layers * depend on this interface so they stay unchanged when the repository * is swapped for a real DB-backed implementation. + * + * Note: uses number IDs and Date types (DB representation). The wire + * format (string IDs, ISO string dates) is defined in @xstreamroll/types. */ export interface Stream { id: number @@ -13,3 +16,13 @@ export interface Stream { createdAt: Date updatedAt: Date } + +export type { + StreamStatus, + StreamVisibility, + CreateStreamDto, + UpdateStreamDto, + StreamEvent, + StreamEventType, + StreamEventRecord, +} from "@xstreamroll/types" diff --git a/api/src/tags/tag.entity.ts b/api/src/tags/tag.entity.ts index 4bb2e81..13d5389 100644 --- a/api/src/tags/tag.entity.ts +++ b/api/src/tags/tag.entity.ts @@ -2,6 +2,9 @@ * In-memory representation of a tag. The shape mirrors what a future * Postgres-backed entity will expose so the controller / service layer * does not need to change when we swap the repository implementation. + * + * Note: uses Date type (DB representation). The wire format (string dates) + * is defined in @xstreamroll/types. */ export interface Tag { id: number @@ -18,3 +21,5 @@ export interface StreamTag { tagId: number createdAt: Date } + +export type { PagedTags } from "@xstreamroll/types" diff --git a/api/tsconfig.json b/api/tsconfig.json index 676c5d8..f66991c 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -14,6 +14,9 @@ "declarationMap": true, "sourceMap": true, "experimentalDecorators": true, - "emitDecoratorMetadata": true + "emitDecoratorMetadata": true, + "paths": { + "@xstreamroll/types": ["../packages/types/src/index.ts"] + } } } diff --git a/app/lib/api/admin-stats.ts b/app/lib/api/admin-stats.ts index 947f64b..5782a2f 100644 --- a/app/lib/api/admin-stats.ts +++ b/app/lib/api/admin-stats.ts @@ -5,13 +5,7 @@ * GET /admin/stats -> AdminStats */ -export interface AdminStats { - totalUsers: number - totalStreams: number - activeStreams: number - eventsLast24h: number - generatedAt: string -} +export type { AdminStats } from "@xstreamroll/types" const DEFAULT_API_BASE = "http://localhost:3001" diff --git a/app/lib/api/notifications.ts b/app/lib/api/notifications.ts index 50de309..6a07b93 100644 --- a/app/lib/api/notifications.ts +++ b/app/lib/api/notifications.ts @@ -5,24 +5,7 @@ * POST /notifications/:id/read -> { id, readAt } */ -export interface Notification { - id: string - /** ISO-8601 timestamp. */ - createdAt: string - /** ISO-8601 timestamp; null when the notification is unread. */ - readAt: string | null - title: string - body: string - /** Optional URL the UI should navigate to on click. */ - href?: string - /** Lightweight category — used to pick the icon. */ - category?: "stream" | "system" | "billing" | "default" -} - -export interface NotificationsPage { - items: Notification[] - unreadCount: number -} +export type { Notification, NotificationsPage } from "@xstreamroll/types" const DEFAULT_API_BASE = "http://localhost:3001" diff --git a/app/lib/api/tags.ts b/app/lib/api/tags.ts index 60e6b9f..08ad419 100644 --- a/app/lib/api/tags.ts +++ b/app/lib/api/tags.ts @@ -6,20 +6,7 @@ * DELETE /streams/:id/tags/:tagId -> 204 */ -export interface Tag { - id: number - name: string - slug: string - createdAt: string -} - -export interface PagedTags { - items: Tag[] - page: number - limit: number - total: number - hasMore: boolean -} +export type { Tag, PagedTags } from "@xstreamroll/types" const DEFAULT_API_BASE = "http://localhost:3001" diff --git a/app/package.json b/app/package.json index a8f300f..4a84317 100644 --- a/app/package.json +++ b/app/package.json @@ -31,6 +31,7 @@ ] }, "dependencies": { + "@xstreamroll/types": "*", "@hookform/resolvers": "^3.10.0", "@radix-ui/react-accordion": "1.2.2", "@radix-ui/react-alert-dialog": "1.1.4", diff --git a/app/tsconfig.json b/app/tsconfig.json index 9c24b3c..d5a7274 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -25,6 +25,9 @@ "paths": { "@/*": [ "./*" + ], + "@xstreamroll/types": [ + "../packages/types/src/index.ts" ] } }, diff --git a/package.json b/package.json index a118352..f25ff73 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "XStreamRoll Platform - Monorepo", "private": true, "workspaces": [ + "packages/types", "app", "api", "xstreamroll-sdk", diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 0000000..245a101 --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,14 @@ +{ + "name": "@xstreamroll/types", + "version": "1.0.0", + "description": "Shared TypeScript types for the XStreamRoll platform", + "main": "src/index.ts", + "types": "src/index.ts", + "private": true, + "scripts": { + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "typescript": "^5.3.0" + } +} diff --git a/packages/types/src/admin-stats.ts b/packages/types/src/admin-stats.ts new file mode 100644 index 0000000..31661e7 --- /dev/null +++ b/packages/types/src/admin-stats.ts @@ -0,0 +1,7 @@ +export interface AdminStats { + totalUsers: number + totalStreams: number + activeStreams: number + eventsLast24h: number + generatedAt: string +} diff --git a/packages/types/src/auth.ts b/packages/types/src/auth.ts new file mode 100644 index 0000000..84714f0 --- /dev/null +++ b/packages/types/src/auth.ts @@ -0,0 +1,27 @@ +export type UserRole = "admin" | "viewer" + +export interface User { + id: string + email: string + displayName: string + role: UserRole + createdAt: string + updatedAt: string +} + +export interface CreateUserDto { + email: string + password: string + displayName: string +} + +export interface UpdateUserDto { + displayName?: string + email?: string +} + +export interface AuthTokens { + accessToken: string + refreshToken: string + expiresIn: number +} diff --git a/packages/types/src/errors.ts b/packages/types/src/errors.ts new file mode 100644 index 0000000..a05b57a --- /dev/null +++ b/packages/types/src/errors.ts @@ -0,0 +1,22 @@ +export interface ValidationError { + field: string + message: string +} + +export interface ApiErrorResponse { + statusCode: number + message: string | string[] + error: string + validationErrors?: ValidationError[] +} + +export class ApiError extends Error { + constructor( + public readonly statusCode: number, + message: string, + public readonly response?: ApiErrorResponse + ) { + super(message) + this.name = "ApiError" + } +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts new file mode 100644 index 0000000..aa8375b --- /dev/null +++ b/packages/types/src/index.ts @@ -0,0 +1,7 @@ +export * from "./admin-stats" +export * from "./auth" +export * from "./errors" +export * from "./notification" +export * from "./pagination" +export * from "./stream" +export * from "./tag" diff --git a/packages/types/src/notification.ts b/packages/types/src/notification.ts new file mode 100644 index 0000000..0d2e85b --- /dev/null +++ b/packages/types/src/notification.ts @@ -0,0 +1,14 @@ +export interface Notification { + id: string + createdAt: string + readAt: string | null + title: string + body: string + href?: string + category?: "stream" | "system" | "billing" | "default" +} + +export interface NotificationsPage { + items: Notification[] + unreadCount: number +} diff --git a/packages/types/src/pagination.ts b/packages/types/src/pagination.ts new file mode 100644 index 0000000..2e4d892 --- /dev/null +++ b/packages/types/src/pagination.ts @@ -0,0 +1,11 @@ +export interface PaginatedResponse { + data: T[] + total: number + page: number + limit: number +} + +export interface PaginationParams { + page?: number + limit?: number +} diff --git a/packages/types/src/stream.ts b/packages/types/src/stream.ts new file mode 100644 index 0000000..1312de4 --- /dev/null +++ b/packages/types/src/stream.ts @@ -0,0 +1,49 @@ +export type StreamStatus = "active" | "inactive" | "error" +export type StreamVisibility = "public" | "private" + +export interface Stream { + id: string + userId: string + name: string + description: string | null + status: StreamStatus + visibility: StreamVisibility + createdAt: string + updatedAt: string +} + +export interface CreateStreamDto { + name: string + description?: string + visibility?: StreamVisibility +} + +export interface UpdateStreamDto { + name?: string + description?: string + status?: StreamStatus + visibility?: StreamVisibility +} + +export type StreamEventType = + | "stream:started" + | "stream:stopped" + | "stream:error" + | "viewer:joined" + | "viewer:left" + | "data" + +export interface StreamEvent { + streamId: string + eventType: StreamEventType + data: Record + timestamp?: string +} + +export interface StreamEventRecord { + id: string + streamId: string + eventType: StreamEventType + payload: Record + occurredAt: string +} diff --git a/packages/types/src/tag.ts b/packages/types/src/tag.ts new file mode 100644 index 0000000..9d3412f --- /dev/null +++ b/packages/types/src/tag.ts @@ -0,0 +1,14 @@ +export interface Tag { + id: number + name: string + slug: string + createdAt: string +} + +export interface PagedTags { + items: Tag[] + page: number + limit: number + total: number + hasMore: boolean +} diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json new file mode 100644 index 0000000..efa1f8d --- /dev/null +++ b/packages/types/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "declaration": true, + "noEmit": true + }, + "include": ["src"] +} diff --git a/xstreamroll-sdk/package.json b/xstreamroll-sdk/package.json index ee7f212..8f4f715 100644 --- a/xstreamroll-sdk/package.json +++ b/xstreamroll-sdk/package.json @@ -24,6 +24,7 @@ ] }, "dependencies": { + "@xstreamroll/types": "*", "axios": "^1.6.0" }, "devDependencies": { diff --git a/xstreamroll-sdk/src/types.ts b/xstreamroll-sdk/src/types.ts index 5f5efd6..3757bde 100644 --- a/xstreamroll-sdk/src/types.ts +++ b/xstreamroll-sdk/src/types.ts @@ -1,6 +1,26 @@ -// ─── Config ────────────────────────────────────────────────────────────────── - -/** Configuration for the StreamingClient. */ +// Re-export all shared types from the platform types package. +export type { + UserRole, + User, + CreateUserDto, + UpdateUserDto, + AuthTokens, + StreamStatus, + StreamVisibility, + Stream, + CreateStreamDto, + UpdateStreamDto, + StreamEventType, + StreamEvent, + StreamEventRecord, + PaginatedResponse, + PaginationParams, + ValidationError, + ApiErrorResponse, +} from "@xstreamroll/types" +export { ApiError } from "@xstreamroll/types" + +// SDK-specific config type — not part of the shared platform contract. export interface StreamConfig { /** @deprecated Use `env` or `baseUrl` instead. */ apiUrl?: string @@ -10,147 +30,3 @@ export interface StreamConfig { /** Explicit base URL. Takes precedence over `env` and `apiUrl`. */ baseUrl?: string } - -// ─── User ───────────────────────────────────────────────────────────────────── - -/** User roles available in the platform. */ -export type UserRole = "admin" | "viewer" - -/** A registered user account. */ -export interface User { - id: string - email: string - displayName: string - role: UserRole - createdAt: string - updatedAt: string -} - -/** Payload for creating a new user. */ -export interface CreateUserDto { - email: string - password: string - displayName: string -} - -/** Payload for updating user profile. */ -export interface UpdateUserDto { - displayName?: string - email?: string -} - -// ─── Auth ───────────────────────────────────────────────────────────────────── - -/** Response returned after a successful login or token refresh. */ -export interface AuthTokens { - accessToken: string - refreshToken: string - expiresIn: number -} - -// ─── Stream ─────────────────────────────────────────────────────────────────── - -/** Possible lifecycle states of a stream. */ -export type StreamStatus = "active" | "inactive" | "error" - -/** Visibility setting for a stream. */ -export type StreamVisibility = "public" | "private" - -/** A stream resource. */ -export interface Stream { - id: string - userId: string - name: string - description: string | null - status: StreamStatus - visibility: StreamVisibility - createdAt: string - updatedAt: string -} - -/** Payload for creating a new stream. */ -export interface CreateStreamDto { - name: string - description?: string - visibility?: StreamVisibility -} - -/** Payload for updating an existing stream. */ -export interface UpdateStreamDto { - name?: string - description?: string - status?: StreamStatus - visibility?: StreamVisibility -} - -// ─── Stream Events ──────────────────────────────────────────────────────────── - -/** Types of events that can occur on a stream. */ -export type StreamEventType = - | "stream:started" - | "stream:stopped" - | "stream:error" - | "viewer:joined" - | "viewer:left" - | "data" - -/** A real-time event emitted by a stream. */ -export interface StreamEvent { - streamId: string - eventType: StreamEventType - data: Record - timestamp?: string -} - -/** A persisted stream event record from the API. */ -export interface StreamEventRecord { - id: string - streamId: string - eventType: StreamEventType - payload: Record - occurredAt: string -} - -// ─── Pagination ─────────────────────────────────────────────────────────────── - -/** Standard paginated API response wrapper. */ -export interface PaginatedResponse { - data: T[] - total: number - page: number - limit: number -} - -/** Query parameters for paginated list endpoints. */ -export interface PaginationParams { - page?: number - limit?: number -} - -// ─── Errors ─────────────────────────────────────────────────────────────────── - -/** A single field-level validation error. */ -export interface ValidationError { - field: string - message: string -} - -/** Standard API error response shape. */ -export interface ApiErrorResponse { - statusCode: number - message: string | string[] - error: string - validationErrors?: ValidationError[] -} - -/** Typed error thrown by the SDK on non-2xx responses. */ -export class ApiError extends Error { - constructor( - public readonly statusCode: number, - message: string, - public readonly response?: ApiErrorResponse - ) { - super(message) - this.name = "ApiError" - } -} diff --git a/xstreamroll-sdk/tsconfig.json b/xstreamroll-sdk/tsconfig.json index 25e427d..36271f0 100644 --- a/xstreamroll-sdk/tsconfig.json +++ b/xstreamroll-sdk/tsconfig.json @@ -12,7 +12,10 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, - "sourceMap": true + "sourceMap": true, + "paths": { + "@xstreamroll/types": ["../packages/types/src/index.ts"] + } }, "include": ["src"], "exclude": ["node_modules"]