/** * Package model for Stack.Gallery Registry */ import * as plugins from '../plugins.ts'; import type { IPackage, IPackageVersion, IProtocolMetadata, } from '../interfaces/package.interfaces.ts'; import type { TRegistryProtocol } from '../interfaces/auth.interfaces.ts'; import { db } from './db.ts'; @plugins.smartdata.Collection(() => db) export class Package extends plugins.smartdata.SmartDataDbDoc implements IPackage { @plugins.smartdata.unI() public id: string = ''; // {protocol}:{org}:{name} @plugins.smartdata.svDb() @plugins.smartdata.index() public organizationId: string = ''; @plugins.smartdata.svDb() @plugins.smartdata.index() public repositoryId: string = ''; @plugins.smartdata.svDb() @plugins.smartdata.index() public protocol: TRegistryProtocol = 'npm'; @plugins.smartdata.svDb() @plugins.smartdata.searchable() @plugins.smartdata.index() public name: string = ''; @plugins.smartdata.svDb() @plugins.smartdata.searchable() public description?: string; @plugins.smartdata.svDb() public versions: Record = {}; @plugins.smartdata.svDb() public distTags: Record = {}; // e.g., { latest: "1.0.0" } @plugins.smartdata.svDb() public metadata: IProtocolMetadata = { type: 'npm' }; @plugins.smartdata.svDb() @plugins.smartdata.index() public isPrivate: boolean = true; @plugins.smartdata.svDb() public storageBytes: number = 0; @plugins.smartdata.svDb() @plugins.smartdata.index() public downloadCount: number = 0; @plugins.smartdata.svDb() public starCount: number = 0; @plugins.smartdata.svDb() public cacheExpiresAt?: Date; @plugins.smartdata.svDb() @plugins.smartdata.index() public createdAt: Date = new Date(); @plugins.smartdata.svDb() public updatedAt: Date = new Date(); @plugins.smartdata.svDb() @plugins.smartdata.index() public createdById: string = ''; /** * Generate package ID */ public static generateId(protocol: TRegistryProtocol, orgName: string, name: string): string { return `${protocol}:${orgName}:${name}`; } /** * Find package by ID */ public static async findById(id: string): Promise { return await Package.getInstance({ id }); } /** * Find package by protocol, org, and name */ public static async findByName( protocol: TRegistryProtocol, orgName: string, name: string ): Promise { const id = Package.generateId(protocol, orgName, name); return await Package.findById(id); } /** * Get packages in an organization */ public static async getOrgPackages(organizationId: string): Promise { return await Package.getInstances({ organizationId }); } /** * Search packages */ public static async search( query: string, options?: { protocol?: TRegistryProtocol; organizationId?: string; isPrivate?: boolean; limit?: number; offset?: number; } ): Promise { const filter: Record = {}; if (options?.protocol) filter.protocol = options.protocol; if (options?.organizationId) filter.organizationId = options.organizationId; if (options?.isPrivate !== undefined) filter.isPrivate = options.isPrivate; // Simple text search - in production, would use MongoDB text index const allPackages = await Package.getInstances(filter); // Filter by query const lowerQuery = query.toLowerCase(); const filtered = allPackages.filter( (pkg) => pkg.name.toLowerCase().includes(lowerQuery) || pkg.description?.toLowerCase().includes(lowerQuery) ); // Apply pagination const offset = options?.offset || 0; const limit = options?.limit || 50; return filtered.slice(offset, offset + limit); } /** * Add a new version */ public addVersion(version: IPackageVersion): void { this.versions[version.version] = version; this.storageBytes += version.size; this.updatedAt = new Date(); } /** * Get specific version */ public getVersion(version: string): IPackageVersion | undefined { return this.versions[version]; } /** * Get latest version */ public getLatestVersion(): IPackageVersion | undefined { const latest = this.distTags['latest']; if (latest) { return this.versions[latest]; } // Fallback to most recent const versionList = Object.keys(this.versions); if (versionList.length === 0) return undefined; return this.versions[versionList[versionList.length - 1]]; } /** * Increment download count */ public async incrementDownloads(version?: string): Promise { this.downloadCount += 1; if (version && this.versions[version]) { this.versions[version].downloads += 1; } await this.save(); } /** * Lifecycle hook */ public async beforeSave(): Promise { this.updatedAt = new Date(); if (!this.id) { this.id = await Package.getNewId(); } } }