-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Description
Proposal: Implement Unbody Push API
Brief Description
Develop a Push API for Unbody to enable seamless record and file management operations in custom collections.
Description and Goal
Create a developer-friendly API that allows developers to easily perform CRUD operations on records and files within the Unbody ecosystem. The goal is to simplify record management and file handling in custom collection, abstract complex API details from developers, and provide an intuitive api for common operations.
Use Cases
- Seamlessly Perform CRUD operations (Create, Read, Update, Delete) on records within a collection.
- Create custom collection.
- Upload, delete, and manage various file types (Image, Audio, Video, TextDocument)
- Handle cross references in custom collections.
Implementation Steps
- Create a base class to handle REST API operations with Axios interceptors, ensuring consistent communication with the Unbody backend apis.
- Design the API interface and class structure, creating a PushApi instance that manages interactions with collections and records using APIKey, projectId, and sourceId.
- Implement record CRUD functionality.
- Develop a record save method utilizing PATCH requests.
- Develop file-handling capabilities supporting various input types (fileBuffer, stream, formData) and handle file validations.
- Implement updating records with file references, enabling multiple file references to be updated simultaneously.
- Process and structure API responses in a developer-friendly format.
- Implement robust error handling and validation.
- Conduct thorough testing across various use cases.
- Create comprehensive documentation and usage examples.
API Interface Design
1. Create a PushApi Instance
const pushApiInstance = new PushApi({
apiKey: "your-api-key",
projectId: "your-project-id",
sourceId: "your-source-id",
});Type Definition for PushApi with Generics
interface PushApiInstance {
collection: <T extends Record>(name: string) => CollectionInstance<T>;
records: {
delete: (collectionName: string, recordId: string) => Promise<void>;
patch: <T extends Record>(
collectionName: string,
recordId: string,
patchObject: Partial<T>
) => Promise<void>;
update: <T extends Record>(
collectionName: string,
recordId: string,
recordObject: T
) => Promise<void>;
};
files: {
create: (
fileRecordId: string,
File: Buffer | Stream | FormData,
metaData?: { fileName: string; mimeType: string }
) => Promise<File>;
delete: (fileId: string) => Promise<void>;
};
}Type Definition for CollectionInstance
interface CollectionInstance<T> {
create: (recordId: string, payload: T) => Promise<T>;
update: (recordId: string, payload: T) => Promise<T>;
patch: (recordId: string, patchObject: Partial<T>) => Promise<T>;
delete: (recordId: string, payload: T) => Promise<void>;
getRecords: () => Promise<T[]>;
getOneRecord: (recordId: string) => Promise<T>;
}2. Using Generics with Collections
To customize the type of the records in a collection, you can define a type for your collection.
Defining a Custom Type for ProfileCollection
import { IVideoBlock, IImageBlock, StringField } from "@unbody-io/ts-client";
// Define the structure for ProfileCollection
type ProfileCollection = {
firstName: StringField;
lastName: StringField;
photos: CrossReferenceField<IImageBlock>;
videos: CrossReferenceField<IVideoBlock>;
};
// Create a collection instance with the custom type
const profileCollectionInstance =
await pushApiInstance.collection<ProfileCollection>("ProfileCollection");Retrieving Records from collectionInstance
// Use the collection instance to retrieve records
const profileRecords: ProfileCollection[] =
await profileCollectionInstance.getRecords();3. CRUD Operations on Collection Instance
Create
// Create a new profile record
const profileCollectionInstance = await pushApi.collection("ProfileCollection");
// create new record from `profileCollectionInstance`
const profileRecordInstance = await profileCollectionInstance.create(
"recordId", // custom recordId
{
firstName: "John",
lastName: "Doe",
birthday: "2000-09-18T18:39:38.026Z",
}
);Update, Patch, and Delete Record
await profileCollection.update("recordId", recordPayloadObject);
await profileCollection.patch("recordId", recordPayloadObject); // Partial update
await profileCollection.delete("recordId");Type Definition for RecordInstance
interface RecordInstance<T extends Record> extends T {
save: () => Promise<void>;
delete: () => Promise<void>;
patch: (data: Partial<T>) => Promise<void>;
update: (data: T) => Promise<void>;
}const profileRecords: Record[] = await profileCollectionInstance.getRecords();
// Find a specific record using JS array `find` method
const specificRecord = profileRecords.find(
(record) => record.firstName === "Jane"
);
// Or find record by recordId from collectionInstance
const specificRecord = await profileCollectionInstance.getOneRecord("recordId");Use record instance to delete, patch, and update the record
await specificRecord.delete(); // Delete the record
await specificRecord.patch({ firstName: "Janet" }); // Partial update
await specificRecord.update({
firstName: "Jane",
lastName: "Smith",
...otherFields,
}); // Full updateFetch Records with Method Chaining
const records: Partial<ProfileCollection[]> = await pushApiInstance
.collection<ProfileCollection>("ProfileCollection")
.getRecords("ProfileCollection")
.limit(10)
.offset(1)
.sort("firstName", "asc")
.exec();4. Record Operations Without a Reference Variable or instance
// Delete a record by explicitly specifying collection name and record ID
await pushApiInstance.records.delete("ProfileCollection", "customProfileId");
// Patch (partial update) a record by collection name and record ID
await pushApiInstance.records.patch<ProfileCollection>(
"ProfileCollection", // CollectionName
"customProfileRecordId",
{
firstName: "Janet",
}
);
// Update (full object replacement) a record by collection name and record ID
await pushApiInstance.records.update<ProfileCollection>(
"ProfileCollection",
"customProfileRecordId",
{
firstName: "Jane",
lastName: "Smith",
}
);5. File Operations
Type Definition for FileInstance
interface FileInstance {
create: (
fileRecordId: string,
File: Buffer | Stream | FormData,
metaData?: { fileName: string; mimeType: string }
) => Promise<File>;
delete: (fileId: string) => Promise<void>;
}Uploading and Deleting Files
const uploadedFile = await pushApiInstance.files.create(
"customFileRecordId",
"Buffer | Stream | FormData",
{
fileName: "example.txt",
mimeType: "text/plain",
}
);
// Delete from uploaded file instance
await uploadedFile.delete();
// Delete a file using its fileId (same as recordId)
await pushApiInstance.files.delete("exampleFileRecordId");
// Alternatively, delete a file from a record. Since files are treated as records:
await pushApiInstance.records.delete("ImageBlock", "exampleFileRecordId");
// Fetch all files
await pushApiInstance.files.getAll();File Record Operations without Reference Variables
// Fetch all files and delete the first one
const files: File[] = await pushApiInstance.files.getAll(); // method chaining (limit, offset, sorting) can be used here also
await files[0].delete(); // Delete the first file6. Cross-Reference Operations
Type Definition for CrossReferenceField
interface CrossReferenceField<T> {
add(item: { id: string; collection: string } | File | File[]): Promise<void>;
set(item: { id: string; collection: string } | File | File[]): Promise<void>;
remove(id: string): Promise<void>;
}Add and Remove Cross-References from profileRecordInstance
// Add files in `photos` cross reference field
await profileRecordInstance.photos.add(uploadedFile);
// Removing a cross-reference
await profileRecordInstance.photos.remove("recordId");
// Add files in bulk `photos` cross reference field
await profileRecordInstance.videos.add([video1, video2, video3]);
// Setting cross-reference
await profileRecordInstance.videos.set([video1, video2, video3]);
await profileRecordInstance.photos.set(uploadedFile);
// Access `photos` cross reference field from profileRecordInstance
const allPhotos = profileRecordInstance.photos;
// Add by crossReferenceRecordId and collectionName
await profileCollectionInstance.photos.add({
id: "imageRecordId",
collection: "ImageBlock",
});Add cross reference using recordId and collectionName
profileCollectionInstance.photos = [
// `photos` cross reference field
{ id: "imageRecordId", collection: "ImageBlock" },
];
// save
await profileCollectionInstance.save();Patch or update a record with cross-reference fields using pushApiInstance
await pushApiInstance.records.patch<ProfileCollection>(
"ProfileCollection",
"recordId",
{
photos: [
// `photos` cross reference field
{
id: "imageRecordId",
collection: "ImageBlock",
},
],
}
);Alternatively, directly patch the record by placing the uploaded file instance
await pushApiInstance.records.patch("customProfileId", "ProfileCollection", {
profilePhoto: [uploadedFile],
});Full update with cross-reference fields
await pushApiInstance.records.update("customProfileId", "ProfileCollection", {
firstName: "Jane",
lastName: "Doe",
profilePhoto: [uploadedFile],
});Metadata
Metadata
Assignees
Labels
No labels