newOS for Developers
Post Actions
Complete API for creating, reading, uploading, and rating posts — the core content units in newOS.
packages/newgraph-signals/src/actions/post.ts (613 lines)
Quick Reference
CRUD Operations
readPost— Fetch post by IDcreatePostSingle— Create with filecreatePostMultiple— Batch createdeletePost— Delete singledeletePosts— Delete multiple
Rating / Voting
rate— Vote on a postmassRate— Vote on multiple
Caching
cachePosts— Store to IndexedDBcachePostsBatchAsync— Batched cachingpostsQuery— Cache query
Utils
getRemoteMeta— Fetch URL metadata
readPost
CRUDFetch a post by ID. Returns cached data first, then fetches from API.
readPost({
id: string; // Required - Post ID
}): ProgressiveHandlerResponse<EnrichedPost>| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Post ID |
API Endpoint
api.post.postList({ id })Nuances
- Auto-starts on call
- Skips API call if cached
contentUrlis "processing" - Caches result to IndexedDB
- Returns early if
progress.doneis already true
Usage
const [post, progress] = readPost({ id: "abc123" });
await progress.value.promise;
console.log(post.value); // EnrichedPostcreatePostSingle
CRUD UploadCreate a post with optional file upload. Handles the full S3 upload flow.
createPostSingle(): ProgressiveHandlerResponse<PostReadResponse>
// Execute with:
progress.exec({
id?: string; // Optional - Pre-set post ID
content?: string; // Text content
contentType?: string; // MIME type
moodId?: string; // Primary folder ID
file?: { // File to upload
name: string;
type: string;
originFileObj: File;
preview?: string; // Base64 thumbnail
};
foldersToAttach?: string[]; // Folder IDs to attach to
})Upload Flow
preparing— Create provisional post with UUID, generate thumbnailcreated— Callapi.post.postCreate()attached— Attach to folders viaattachToFolders()upload-requested— Request presigned S3 URL viaapi.post.uploadCreate()uploaded— PUT file to S3 presigned URL
API Endpoints
api.post.postCreate(postForm)api.post.uploadCreate({ filename, targetId, contentType })PUT {presignedUrl} — Direct S3 uploadNuances
- Generates base64 thumbnail via
resizeImage() - Uses
retry()wrapper for resilience on all API calls - Manages upload queue via
uploadQueueSignal - Post must have content OR file (not both optional)
- Generates UUID via
uuidv4()for provisional post - Sets
contentUrl: "preparing"during upload - Uses
wait(3)delays between steps for API stability
Provisional Post Structure
{
id: "uuid-generated",
thumbUrl: file.preview,
contentUrl: base64thumb,
label: "post",
author: currentUser,
updated: now,
created: now,
contentType: file.type || "text/plain",
uploadState: {
blob: file.originFileObj,
filename: file.name,
thumb: base64thumb,
done: false,
status: "preparing"
}
}createPostMultiple
CRUD UploadCreate multiple posts at once with content distribution options.
createPostMultiple(): ProgressiveHandlerResponse<UploadProgressEntry[]>
// Execute with:
progress.exec({
files: File[]; // Files to upload
content?: string; // Text content
contentMode: "last" | "first" | "each"; // Where to add content
foldersToAttach?: string[];
})Content Modes
last— Add content to last file onlyfirst— Add content to first file onlyeach— Add content to every file
Nuances
- Queues uploads via
uploadQueueSignal - Each file creates an
UploadProgressEntry - Uses
createPostSingleuploader internally - Returns
done: falseimmediately while queue processes
deletePost
CRUDDelete a post. Handles both prepared and committed posts.
deletePost(): ProgressiveHandlerResponse<null>
// Execute with:
progress.exec({ id: string })API Endpoint
api.post.postDelete({ id })Nuances
- If post
contentUrlis "preparing", just deletes from cache (not committed yet) - Otherwise calls API then marks as
deleted: truein cache
deletePosts
CRUDDelete multiple posts at once.
deletePosts(): ProgressiveHandlerResponse<null>
// Execute with:
progress.exec({ ids: string[] })Nuances
- Uses
forEachwith async (not properly awaited — potential race condition) - Same logic as
deletePostfor each ID
rate
VotingVote/rate a post. Uses a queue to prevent concurrent rating calls.
rate({
targetId: string; // Post ID
}): ProgressiveHandlerResponse<RatingUpdateResponse>
// Execute with:
progress.exec({
targetId: string;
value: number; // Rating value (e.g., 0-100)
contextType?: string;
contextValue?: string;
})| Parameter | Type | Required | Description |
|---|---|---|---|
| targetId | string | Yes | Post ID to rate |
| value | number | Yes | Rating value |
| contextType | string | No | Context identifier type |
| contextValue | string | No | Context identifier value |
API Endpoint
api.post.rateCreate({ targetId, value })Queue Mechanism
Uses rateQueue array and rateConcurrencyLock to ensure only one rating call executes at a time. Calls are queued and processed sequentially via processRateQueue().
Nuances
- Updates cache with
votefield after successful API call - Returns immediately, actual rating happens async in queue
massRate
VotingVote/rate multiple posts with the same value.
massRate({
targetIds: string[]; // Post IDs
}): ProgressiveHandlerResponse<RatingUpdateResponse>
// Execute with:
progress.exec({
targetIds: string[];
value: number;
})Nuances
- Adds each post to the rating queue
- All posts rated with same value
- Uses same queue mechanism as
rate
cachePosts
CachingStore posts to IndexedDB with optional folder attachment.
cachePosts(
posts: PostReadResponse | EnrichedPost | PostReadResponse[] | EnrichedPost[],
folders?: MoodReadResponse[],
relName?: string | string[], // Edge label (default: "attachment")
opts?: {
appendProps?: (keyof EnrichedPost)[]; // Append to existing values
ignoreShorter?: (keyof EnrichedPost)[]; // Keep longer cached values
}
): Promise<void>| Parameter | Type | Description |
|---|---|---|
| posts | Post[] | Post(s) to cache |
| folders | Mood[] | Additional folders to create edges for |
| relName | string | Edge label (default: "attachment") |
| opts.appendProps | string[] | Concatenate string values instead of replacing |
| opts.ignoreShorter | string[] | Keep cached value if incoming is shorter |
Edge Creation
Creates edges for all folders in: folders param + post.moods + post.moodId
Nuances
- Skips caching if incoming
updatedis older than cached - Parses
aiMetafrom JSON string if it's a string - Clears
uploadStateoncecontentUrlis set and not base64 - Sets
contentUrlto empty string if it was "processing" - Uses
bulkPutDelayedfor batch efficiency - Skips "boring" posts (only id + label)
cachePostsBatchAsync
CachingBatched version of cachePosts for high-throughput scenarios.
cachePostsBatchAsync: BatchedFunction
// Batches: 100 items, 10s max retentionpostsQuery
Cache QueryQuery posts from IndexedDB cache.
postsQuery(id: string | string[]): Promise<EnrichedPost[]>Implementation
cache.post.where("id").anyOf(ids).toArray()getRemoteMeta
UtilityFetch metadata from a remote URL (Open Graph, title, etc).
getRemoteMeta({ url: string }): Promise<PostRemoteMetaProxyResponse>API Endpoint
api.post.utilsRemoteMetaProxyList({ url })Use Case
Used for link previews when pasting URLs into posts. Extracts Open Graph metadata, page titles, and thumbnails.
Key Types
EnrichedPost
type EnrichedPost = PostReadResponse & {
uploadState?: PostUploadState;
thumbUrl?: string;
aiMeta?: AIMeta;
reasoning?: string[];
relativeRating?: number; // Computed relative to folder
}PostUploadState
type PostUploadState = {
blob?: Blob;
filename?: string;
thumb?: string; // Base64 thumbnail
done: boolean;
status: "preparing" | "created" | "attached" |
"upload-requested" | "uploaded";
}PostMaybeWithThumb
type PostMaybeWithThumb = PostReadResponse & {
thumbUrl?: string;
label?: string;
uploadState: PostUploadState;
}UploadProgressEntry
type UploadProgressEntry = {
localId: string;
file: File;
contentUrl: string;
content: string;
progress: string;
foldersToAttach: string[];
}URL Construction
GET /api/post/{ id }POST /api/postPOST /api/post/uploadPOST /api/post/rateDELETE /api/post/{ id }GET /api/post/utils/remote-meta-proxy?url={url}Upload State Sequence
preparing→created→attached→upload-requested→uploadedState Details
preparingProvisional post created in cache with UUID and base64 thumbnailcreatedAPI call completed, post exists on serverattachedPost attached to specified foldersupload-requestedPresigned S3 URL obtaineduploadedFile successfully PUT to S3, processing begins server-sideUpload Queue Management
The upload system uses uploadQueueSignal to manage concurrent uploads:
- New uploads are added to the queue
- Queue processes one upload at a time
- After each upload,
doNextJob()shifts to next item - Semaphore prevents duplicate execution
Edge Cases & Gotchas
contentUrl === "processing", the post is waiting for server-side media processing. readPost skips API calls for these.contentUrl === "preparing" are local-only and can be deleted from cache without API call.aiMeta field may come as a JSON string from the API. cachePosts automatically parses it.createPostSingle throws an error. Always ensure file.type is set.