@smartsamurai/krapi-sdk
Version:
KRAPI TypeScript SDK - Easy-to-use client SDK for connecting to self-hosted KRAPI servers (like Appwrite SDK)
304 lines (290 loc) • 10.1 kB
text/typescript
/**
* Collections Adapter
*
* Unifies CollectionsHttpClient and CollectionsService/CollectionsSchemaManager behind a common interface.
*/
import { CollectionsSchemaManager } from "../../collections-schema-manager";
import { CollectionsService } from "../../collections-service";
import { KrapiError } from "../../core/krapi-error";
import { CollectionsHttpClient } from "../../http-clients/collections-http-client";
import { Collection, FieldType } from "../../types";
import { createAdapterInitError } from "./error-handler";
type Mode = "client" | "server";
export class CollectionsAdapter {
private mode: Mode;
private httpClient: CollectionsHttpClient | undefined;
private service: CollectionsService | undefined;
private schemaManager: CollectionsSchemaManager | undefined;
constructor(
mode: Mode,
httpClient?: CollectionsHttpClient,
service?: CollectionsService,
schemaManager?: CollectionsSchemaManager
) {
this.mode = mode;
this.httpClient = httpClient;
this.service = service;
this.schemaManager = schemaManager;
}
async create(
projectId: string,
collectionData: {
name: string;
description?: string;
fields: Array<{
name: string;
type: string;
required?: boolean;
unique?: boolean;
indexed?: boolean;
default?: unknown;
validation?: Record<string, unknown>;
}>;
indexes?: Array<{
name: string;
fields: string[];
unique?: boolean;
}>;
}
): Promise<Collection> {
if (this.mode === "client") {
if (!this.httpClient) {
throw createAdapterInitError("HTTP client", this.mode);
}
const response = await this.httpClient.createCollection(projectId, collectionData);
if (response && typeof response === "object") {
if ("collection" in response && response.collection) {
return response.collection as unknown as Collection;
}
if ("data" in response && response.data) {
return response.data as unknown as Collection;
}
if ("id" in response && "name" in response) {
return response as unknown as Collection;
}
}
return {} as Collection;
} else {
if (!this.schemaManager) {
throw createAdapterInitError("Collections schema manager", this.mode);
}
const result = await this.schemaManager.createCollection({
...collectionData,
fields: collectionData.fields.map((f) => ({
...f,
type: f.type as FieldType,
required: f.required ?? false,
unique: f.unique ?? false,
indexed: f.indexed ?? false,
})),
});
result.project_id = projectId;
return result as unknown as Collection;
}
}
async get(projectId: string, collectionName: string): Promise<Collection> {
if (this.mode === "client") {
if (!this.httpClient) {
throw createAdapterInitError("HTTP client", this.mode);
}
const response = await this.httpClient.getCollection(projectId, collectionName);
const collection = response.data as unknown as Collection;
if (!collection) {
throw KrapiError.notFound(`Collection '${collectionName}' not found in project '${projectId}'`, { collectionName, projectId });
}
return collection;
} else {
if (!this.service) {
throw createAdapterInitError("Collections service", this.mode);
}
// Use CollectionsService.getCollection which supports UUID and case-insensitive name lookup
const collection = await this.service.getCollection(projectId, collectionName);
if (!collection) {
throw KrapiError.notFound(`Collection '${collectionName}' not found in project '${projectId}'`, { collectionName, projectId });
}
return collection;
}
}
async getAll(
projectId: string,
options?: {
limit?: number;
offset?: number;
search?: string;
}
): Promise<Collection[]> {
if (this.mode === "client") {
if (!this.httpClient) {
throw createAdapterInitError("HTTP client", this.mode);
}
const response = await this.httpClient.getProjectCollections(projectId, options);
if (response && typeof response === "object") {
if ("collections" in response && Array.isArray(response.collections)) {
return response.collections as unknown as Collection[];
}
if ("data" in response && Array.isArray(response.data)) {
return response.data as unknown as Collection[];
}
if (Array.isArray(response)) {
return response as unknown as Collection[];
}
}
return [];
} else {
if (!this.schemaManager) {
throw createAdapterInitError("Collections schema manager", this.mode);
}
const collections = await this.schemaManager.getCollections();
return collections.filter((c) => c.project_id === projectId) as unknown as Collection[];
}
}
async update(
projectId: string,
collectionName: string,
updates: {
description?: string;
fields?: Array<{
name: string;
type: string;
required?: boolean;
unique?: boolean;
indexed?: boolean;
default?: unknown;
validation?: Record<string, unknown>;
}>;
indexes?: Array<{
name: string;
fields: string[];
unique?: boolean;
}>;
}
): Promise<Collection> {
if (this.mode === "client") {
if (!this.httpClient) {
throw createAdapterInitError("HTTP client", this.mode);
}
const response = await this.httpClient.updateCollection(projectId, collectionName, updates);
return (response.data as unknown as Collection) || ({} as Collection);
} else {
const collection = await this.get(projectId, collectionName);
if (!collection) {
throw KrapiError.notFound("Collection not found");
}
if (!this.schemaManager) {
throw createAdapterInitError("Collections schema manager", this.mode);
}
return this.schemaManager.updateCollection(collection.id, updates as Partial<Collection>) as unknown as Collection;
}
}
async delete(projectId: string, collectionName: string): Promise<{ success: boolean }> {
if (this.mode === "client") {
if (!this.httpClient) {
throw createAdapterInitError("HTTP client", this.mode);
}
const response = await this.httpClient.deleteCollection(projectId, collectionName);
return { success: response.data?.success || false };
} else {
const collection = await this.get(projectId, collectionName);
if (!collection) {
return { success: false };
}
if (!this.schemaManager) {
throw createAdapterInitError("Collections schema manager", this.mode);
}
const success = await this.schemaManager.deleteCollection(collection.id);
return { success };
}
}
async getSchema(projectId: string, collectionName: string): Promise<Collection> {
return this.get(projectId, collectionName);
}
async validateSchema(
projectId: string,
collectionName: string
): Promise<{
valid: boolean;
issues: Array<{
type: string;
field?: string;
message: string;
severity: "error" | "warning" | "info";
}>;
}> {
if (this.mode === "client") {
if (!this.httpClient) {
throw createAdapterInitError("HTTP client", this.mode);
}
const response = await this.httpClient.validateCollectionSchema(projectId, collectionName);
return response.data || { valid: false, issues: [] };
} else {
if (!this.schemaManager) {
throw createAdapterInitError("Collections schema manager", this.mode);
}
const collection = await this.get(projectId, collectionName);
if (!collection) {
throw KrapiError.notFound(`Collection '${collectionName}' not found in project '${projectId}'`, { collectionName, projectId });
}
const validation = await this.schemaManager.validateCollectionSchema(collection.id);
const issues = validation.issues.map((issue) => {
let severity: "error" | "warning" | "info" = "error";
if (issue.type === "extra_field") {
severity = "warning";
} else if (issue.type === "missing_index") {
severity = "info";
}
return {
type: issue.type,
field: issue.field,
message: issue.description,
severity,
};
});
return {
valid: validation.isValid,
issues: issues.map((issue) => {
const result: {
type: string;
field?: string;
message: string;
severity: "info" | "warning" | "error";
} = {
type: issue.type,
message: issue.message,
severity: issue.severity,
};
if (issue.field !== undefined) {
result.field = issue.field;
}
return result;
}),
};
}
}
async getStatistics(
projectId: string,
collectionName: string
): Promise<{ total_documents: number; total_size_bytes: number }> {
if (this.mode === "client") {
if (!this.httpClient) {
throw createAdapterInitError("HTTP client", this.mode);
}
const response = await this.httpClient.getCollectionStatistics(projectId, collectionName);
return (
(response.data as unknown as {
total_documents: number;
total_size_bytes: number;
}) || { total_documents: 0, total_size_bytes: 0 }
);
} else {
if (!this.service) {
throw createAdapterInitError("Collections service", this.mode);
}
const documents = await this.service.getDocuments(projectId, collectionName);
const totalSize = JSON.stringify(documents).length;
return {
total_documents: documents.length,
total_size_bytes: totalSize,
};
}
}
}