@smartsamurai/krapi-sdk
Version:
KRAPI TypeScript SDK - Easy-to-use client SDK for connecting to self-hosted KRAPI servers (like Appwrite SDK)
369 lines (347 loc) • 11 kB
text/typescript
import { AxiosInstance } from "axios";
import { CollectionsHttpClient } from "../http-clients/collections-http-client";
import { ApiResponse } from "../types";
import { ResponseNormalizer } from "../utils/response-normalizer";
/**
* Collections API Manager
*
* Handles all collection and document-related API operations for the KRAPI client SDK.
*/
export class CollectionsApiManager {
private axiosInstance: AxiosInstance;
private collectionsClient: CollectionsHttpClient;
constructor(axiosInstance: AxiosInstance, collectionsClient: CollectionsHttpClient) {
this.axiosInstance = axiosInstance;
this.collectionsClient = collectionsClient;
}
/**
* Documents API methods
*/
documents = {
/**
* List documents in a collection
*/
list: async (
projectId: string,
collectionName: string,
options?: {
limit?: number;
offset?: number;
filter?: Record<string, unknown>;
sort?: Record<string, "asc" | "desc">;
}
): Promise<ApiResponse<unknown[]>> => {
// Construct correct endpoint: /krapi/k1/projects/{projectId}/collections/{collectionName}/documents
const url = `/projects/${projectId}/collections/${collectionName}/documents`;
const params = new URLSearchParams();
if (options?.limit) params.append("limit", options.limit.toString());
if (options?.offset) params.append("offset", options.offset.toString());
if (options?.filter) params.append("filter", JSON.stringify(options.filter));
if (options?.sort) params.append("sort", JSON.stringify(options.sort));
// Let interceptor handle all headers - no explicit headers needed
const response = await this.axiosInstance.get<
| ApiResponse<unknown[]>
| {
success: boolean;
data?: { documents?: unknown[]; [key: string]: unknown };
documents?: unknown[];
}
>(
`/krapi/k1${url}${params.toString() ? `?${params.toString()}` : ""}`
);
// Normalize response format using utility
const normalizedData = ResponseNormalizer.normalizeDocumentListResponse(response.data);
return {
success: true,
data: normalizedData,
};
},
/**
* Get a single document
*/
get: async (
projectId: string,
collectionName: string,
documentId: string
): Promise<ApiResponse<unknown>> => {
const response = await this.collectionsClient.getDocument(projectId, collectionName, documentId);
const result: ApiResponse<unknown> = {
success: response.success,
data: ResponseNormalizer.normalizeDocumentResponse(response.data),
};
if (response.error) {
result.error = response.error;
}
return result;
},
/**
* Create a new document
*/
create: async (
projectId: string,
collectionName: string,
data: Record<string, unknown>
): Promise<ApiResponse<unknown>> => {
// Construct correct endpoint: /krapi/k1/projects/{projectId}/collections/{collectionName}/documents
// Let interceptor handle all headers - Content-Type is already in defaults
const response = await this.axiosInstance.post<
| ApiResponse<unknown>
| {
success: boolean;
data?: unknown;
id?: string;
[key: string]: unknown;
}
>(
`/krapi/k1/projects/${projectId}/collections/${collectionName}/documents`,
{ data, created_by: "client" }
);
// Normalize response format using utility
const normalized = ResponseNormalizer.normalizeDocumentResponse(response.data);
return {
success: true,
data: normalized,
};
},
/**
* Update a document
*/
update: async (
projectId: string,
collectionName: string,
documentId: string,
data: Record<string, unknown>
): Promise<ApiResponse<unknown>> => {
return this.collectionsClient.updateDocument(
projectId,
collectionName,
documentId,
{ data, updated_by: "client" }
);
},
/**
* Delete a document
*/
delete: async (
projectId: string,
collectionName: string,
documentId: string
): Promise<ApiResponse<{ success: boolean }>> => {
return this.collectionsClient.deleteDocument(projectId, collectionName, documentId);
},
/**
* Bulk create documents
*/
bulkCreate: async (
projectId: string,
collectionName: string,
documents: Record<string, unknown>[]
): Promise<
ApiResponse<{
created: unknown[];
errors: Array<{ index: number; error: string }>;
}>
> => {
// Convert array to CreateDocumentRequest[]
const createRequests = documents.map((doc) => ({
data: doc,
created_by: "client",
}));
return this.collectionsClient.bulkCreateDocuments(projectId, collectionName, createRequests);
},
/**
* Bulk update documents with filter
*/
bulkUpdate: async (
projectId: string,
collectionName: string,
filter: Record<string, unknown>,
updates: Record<string, unknown>
): Promise<ApiResponse<unknown>> => {
// For bulk update with filter, we need to use a different endpoint
// This is a simplified version - may need to adjust based on actual API
const response = await this.axiosInstance.put<ApiResponse<unknown>>(
`/krapi/k1/projects/${projectId}/collections/${collectionName}/documents/bulk`,
{ filter, updates }
);
return response.data;
},
/**
* Bulk delete documents with filter
*/
bulkDelete: async (
projectId: string,
collectionName: string,
filter: Record<string, unknown>
): Promise<ApiResponse<{ success: boolean }>> => {
// For bulk delete with filter, we need to fetch IDs first or use a filter endpoint
// This is a simplified version - may need to adjust based on actual API
const response = await this.axiosInstance.post<
ApiResponse<{ success: boolean }>
>(
`/krapi/k1/projects/${projectId}/collections/${collectionName}/documents/bulk-delete`,
{ filter }
);
return response.data;
},
/**
* Search documents
*/
search: async (
projectId: string,
collectionName: string,
query: string,
options?: {
fields?: string[];
limit?: number;
}
): Promise<ApiResponse<unknown[]>> => {
const searchOptions: {
text: string;
fields?: string[];
limit?: number;
} = {
text: query,
};
if (options?.fields !== undefined) searchOptions.fields = options.fields;
if (options?.limit !== undefined) searchOptions.limit = options.limit;
return this.collectionsClient.searchDocuments(projectId, collectionName, searchOptions);
},
/**
* Aggregate documents
*/
aggregate: async (
projectId: string,
collectionName: string,
options: {
groupBy?: string;
operations: Array<{ field: string; operation: string }>;
}
): Promise<
ApiResponse<{
groups: Record<string, Record<string, number>>;
total_groups: number;
}>
> => {
const aggregations: Record<
string,
{ type: "count" | "sum" | "avg" | "min" | "max"; field?: string }
> = {};
if (options.operations) {
for (const op of options.operations) {
const opType = op.operation as "count" | "sum" | "avg" | "min" | "max";
if (["count", "sum", "avg", "min", "max"].includes(op.operation)) {
aggregations[op.field] = { type: opType, field: op.field };
}
}
}
const aggregateOptions: {
group_by?: string[];
aggregations: Record<
string,
{ type: "count" | "sum" | "avg" | "min" | "max"; field?: string }
>;
} = {
aggregations,
};
if (options.groupBy) {
aggregateOptions.group_by = [options.groupBy];
}
return this.collectionsClient.aggregateDocuments(projectId, collectionName, aggregateOptions);
},
};
/**
* Collection management methods
*/
collections = {
/**
* List collections in a project
*/
list: async (projectId: string): Promise<ApiResponse<unknown[]>> => {
const result = await this.collectionsClient.getProjectCollections(projectId);
// Handle different response formats
if (result && typeof result === "object") {
if ("collections" in result && Array.isArray(result.collections)) {
return {
success: true,
data: result.collections,
} as ApiResponse<unknown[]>;
}
if ("data" in result && Array.isArray(result.data)) {
return {
success: true,
data: result.data,
} as ApiResponse<unknown[]>;
}
}
return {
success: true,
data: [],
} as ApiResponse<unknown[]>;
},
/**
* Get a single collection
*/
get: async (
projectId: string,
collectionName: string
): Promise<ApiResponse<unknown>> => {
return this.collectionsClient.getCollection(projectId, collectionName);
},
/**
* Create a new collection
*/
create: async (
projectId: string,
collectionData: {
name: string;
description?: string;
fields: Array<{
name: string;
type: string;
required?: boolean;
default?: unknown;
}>;
indexes?: Array<{
name: string;
fields: string[];
unique?: boolean;
}>;
}
): Promise<ApiResponse<unknown>> => {
const response = await this.collectionsClient.createCollection(
projectId,
collectionData
);
// Normalize response using utility
const normalizedData = ResponseNormalizer.normalizeCollectionResponse(response.data);
const result: ApiResponse<unknown> = {
success: response.success,
data: normalizedData,
};
if (response.error) {
result.error = response.error;
}
return result;
},
/**
* Update a collection
*/
update: async (
projectId: string,
collectionName: string,
updates: Record<string, unknown>
): Promise<ApiResponse<unknown>> => {
return this.collectionsClient.updateCollection(projectId, collectionName, updates);
},
/**
* Delete a collection
*/
delete: async (
projectId: string,
collectionName: string
): Promise<ApiResponse<{ success: boolean }>> => {
return this.collectionsClient.deleteCollection(projectId, collectionName);
},
};
}