newOS for Developers

Folder / Mood Actions

Complete API for creating, reading, and managing folders (moods/spaces) — the primary content containers in newOS.

packages/newgraph-signals/src/actions/folder.ts (1166 lines)

Quick Reference

CRUD Operations

  • readFolder — Fetch folder by ID
  • createFolder — Create new folder
  • updateFolder — Update folder props
  • readMultipleFolder — Fetch multiple folders
  • downloadFolder — Export folder as JSON

Post Management

  • readPosts — Get folder's posts
  • attachToFolder — Attach post
  • massAttachToFolder — Bulk attach
  • attachToFolders — Post to multiple folders
  • cachePostAttachment — Cache edges

Access Control

  • requestAccess — Request access
  • grantWriteAccess — Grant to user
  • grantWriteAccessMulti — Grant to multiple
  • readFolderGrantees — List grantees
  • cacheGrants — Cache grant edges

Cache Queries

  • folderQuery — Get from cache
  • foldersQuery — Get multiple
  • folderPosts — Cached posts
  • grantees — Cached grantees
  • topFolders — Public folders

Discovery

  • readTopFolders — List top public folders
  • getOneOnOneFolder — DM folder
  • liveSubscribe — WebSocket updates

Caching Utilities

  • cacheFolders — Store to IndexedDB
  • cacheFoldersBatchAsync — Batched caching

readFolder

CRUD

Fetch a folder/mood by ID. Returns cached data first, then fetches from API.

readFolder({
  id: string;              // Required - Folder ID
  preferPublic?: boolean;  // Optional - Use public endpoint
}): ProgressiveHandlerResponse<EnrichedFolder>
ParameterTypeRequiredDescription
idstringYesFolder/mood ID
preferPublicbooleanNoIf true, uses public endpoint even when logged in

API Endpoints

api.mood.readPrivateList({ id }) — When logged in
api.mood.moodList({ id }) — Public endpoint

Nuances

  • Auto-starts on call (no need to call .exec())
  • Caches folder and its posts to IndexedDB
  • Sets accessLevel to empty string if missing
  • HTTP 403 throws error, not cached as private
  • Uses _current.value.id to determine if user is logged in

Usage

const [folder, progress] = readFolder({ id: "abc123" });
// Auto-starts, no exec needed

await progress.value.promise;
console.log(folder.value); // EnrichedFolder

readMultipleFolder

CRUD

Fetch multiple folders in parallel by their IDs.

readMultipleFolder({
  ids: string[];  // Required - Array of folder IDs
}): ProgressiveHandlerResponse<EnrichedFolder[]>

API Endpoint

api.mood.moodList({ id }) — Called for each ID in parallel

Nuances

  • Auto-starts on call
  • Uses Promise.all for parallel fetching
  • Caches each folder and its posts individually
  • Returns cached data first via foldersQuery(ids)

Usage

const [folders, progress] = readMultipleFolder({ 
  ids: ["folder1", "folder2", "folder3"] 
});
await progress.value.promise;
console.log(folders.value); // EnrichedFolder[]

readPosts

CRUD

Fetch all posts attached to a folder with sorting and filtering.

readPosts({
  id: string;              // Required - Folder ID
  sortBy?: string;         // Optional - Sort field (default: "created")
  reverse?: boolean;       // Optional - Desc order (default: true)
  noTextNodes?: boolean;   // Optional - Exclude text-only posts
  noPrivate?: boolean;     // Optional - Exclude private posts
  loadAll?: boolean;       // Optional - Paginate through all (default: true)
  access?: "private" | "public" | "auto";  // Optional
}, opts?: { readAll: boolean }): ProgressiveHandlerResponse<EnrichedPost[]>
ParameterTypeDefaultDescription
idstringFolder ID (required)
sortBystring"created"Sort field
reversebooleantrueDescending order
noTextNodesbooleanfalseExclude text/plain posts
noPrivatebooleanfalseExclude private posts
loadAllbooleantrueAuto-paginate all pages
accessstring"auto"Force private/public endpoint

Sorting Options

  • created — Default, by creation date
  • users — By author watts, then rating (advanced)
  • points — Group by author, sum ratings (advanced)

API Endpoints

api.mood.attachmentsList({ id, page, sortBy, order }) — When logged in
api.mood.attachmentsPublicList({ id, page, sortBy, order }) — Public

Nuances

  • Computes relativeRating for posts with /card content
  • Filters deleted posts automatically
  • Stores edges: folder+id+attachment+post
  • Default page size: 50 (or 1000 if readAll)
  • Supports -text/plain contentType filter via query param

createFolder

CRUD

Create a new folder/mood. Supports "future" folders (local-only until committed).

createFolder({
  key?: string;  // Optional - Deduplication key
}): ProgressiveHandlerResponse<MoodCreateResponse[]>

// Execute with:
progress.exec({
  id?: string;                  // Optional - Pre-set ID
  title?: string;
  description?: string;
  isPrivate?: boolean;          // Default: true
  licenseType?: string;         // Default: "private" if future
  flow?: string;                // AI flow settings
  defaultView?: string;         // e.g., "chat"
  future?: boolean;             // Local-only until committed
  futureGrantees?: { id: string }[];  // Grant access on commit
  failIfExists?: boolean;       // Throw if duplicate
  ignoreExistingFuture?: boolean;     // Skip future check
})

Future Folders

When future: true, the folder is stored locally with a UUID. No API call is made. The folder is committed to the API when you call again with future: false.

API Endpoint

api.mood.moodCreate(folderForm)

Flow Flags

  • web — Standard web folder
  • swarm — Collaborative AI folder
  • blank — No AI flow, used for DM chats

Nuances

  • Generates UUID via uuidv4() for future folders
  • Auto-grants access to futureGrantees on commit
  • Sets author to _current.value
  • Has onReset callback to clear response signal

updateFolder

CRUD

Update folder properties. Auto-commits future folders.

updateFolder(): ProgressiveHandlerResponse<MoodReadResponse[]>

// Execute with:
progress.exec({
  id: string;          // Required - Folder ID
  title?: string;
  description?: string;
  flow?: string;
  isPrivate?: boolean;
  future?: boolean;    // Keep as future folder
})

API Endpoint

api.mood.moodUpdate(folderForm)

Nuances

  • If cached folder is future and form is not, commits to API first via createFolder
  • Updates cache in-place with .modify()
  • Appends to response signal array

attachToFolder

Attachment

Attach a post to a folder. Creates the edge relationship.

attachToFolder(
  post: PostReadResponse,
  folder: MoodReadResponse
): Promise<void>

API Endpoint

api.mood.attachPostUpdate({ targetId: post.id, id: folder.id })

Related Functions

  • massAttachToFolder(posts, folder) — Attach multiple posts sequentially to one folder
  • attachToFolders(post, folders) — Attach one post to multiple folders in parallel
  • attachToFolderProgressive() — ProgressiveHandler variant with progress tracking
  • attachToFolderProgressiveMulti() — Batch attach with progress

Attacheable Labels

  • postapi.mood.attachPostUpdate
  • moodapi.mood.attachMoodUpdate
  • userapi.mood.attachUserUpdate

requestAccess

Access Control

Request access to a private folder.

requestAccess({
  folder: MoodReadResponse;
}): Promise<void>

API Endpoint

api.mood.accessRequestCreate({ targetId: folder.id })

Nuances

  • Updates cache with accessLevel: "request"
  • Creates edge: user+id+access+folder with level "request"
  • Uses current user from _current.value

grantWriteAccess

Access Control

Grant a user access to a folder.

grantWriteAccess({
  user: UserReadPublicResponse;
  folder: MoodReadResponse;
  accessLevel?: string;  // Default: "write"
}): Promise<void>

API Endpoint

api.mood.accessGrantCreate({ targetId, grantee, policy })

grantWriteAccessMulti

ProgressiveHandler variant for granting access to multiple users/folders. Handles future folder scenarios.

grantWriteAccessMulti(): ProgressiveHandlerResponse<{ redirect: string }>

// Execute with:
progress.exec({
  users: UserReadPublicResponse[];
  folders: EnrichedFolder[];
  ignoreOneOnOne?: boolean;  // Convert oneOnOne to regular folder
})

readFolderGrantees

Access Control

List all users with access to a folder.

readFolderGrantees({
  id: string;  // Folder ID
}): ProgressiveHandlerResponse<UserReadPublicResponseWithAccess[]>

API Endpoint

api.mood.accessGranteesList({ id, page })

Nuances

  • Auto-starts with 1.5s delay for debouncing
  • Uses cacheGrants to store edges
  • Paginated response

readTopFolders

Discovery

Fetch paginated list of top public folders.

readTopFolders(params?: {
  page?: number;
}): ProgressiveHandlerResponse<EnrichedFolder[]>

API Endpoint

api.mood.listTopList({ page })

getOneOnOneFolder

Special

Get or create a direct message folder between two users.

getOneOnOneFolder({
  id?: string;        // Target user ID
  username?: string;  // Target username
}): ProgressiveHandlerResponse<EnrichedFolder>

ID Construction

OneOnOne folder ID = [userId1, userId2].sort().join("_")

API Endpoint

api.mood.oneononeList({ id })

Nuances

  • Chat title format: username1 - username2
  • Sets defaultView: "chat" and flow: "blank"
  • Auto-triggers readFolderGrantees for the new folder

liveSubscribe

Real-time

Subscribe to real-time updates for a folder via WebSocket.

liveSubscribe(folderId: string): Promise<void>

WebSocket Message

{ action: "subscribe", targetId: folderId }

Nuances

  • Only subscribes once per folder (tracked in subscribed dictionary)
  • Uses newgraphWebsocketsClient for connection

downloadFolder

Export

Export a folder's contents as a JSON file download.

downloadFolder({
  folderId: string;
}): ProgressiveHandlerResponse<null>

Nuances

  • Calls readPosts with loadAll: true
  • Downloads as folderId_folder_data_timestamp.json
  • Does not auto-start (must call exec())

Cache Queries

IndexedDB

Query cached data without API calls.

folderQuery / foldersQuery

folderQuery(id: string): Promise<EnrichedFolder>
foldersQuery(id: string | string[]): Promise<EnrichedFolder[]>

Returns folders from cache.folder table, sorted by created date ascending.

folderPosts

folderPosts(id: string, opts?: {
  sortBy?: string;
  reverse?: boolean;
  noTextNodes?: boolean;
  noPrivate?: boolean;
}): Promise<EnrichedPost[]>

Uses edge table: folder+id+attachment+post. Supports advanced sorting modes.

grantees

grantees(id: string, opts?: {
  sortBy?: string;
  reverse?: boolean;
  accessLevel?: string;
}): Promise<UserReadPublicResponseWithAccess[]>

Uses edge table: folder+id+access+user. Returns futureGrantees for future folders.

topFolders

topFolders(id?: string, opts?: {
  sortBy?: string;
  reverse?: boolean;
}): Promise<EnrichedFolder[]>

Filters out private folders from cache.

Caching Functions

IndexedDB

cacheFolders

cacheFolders(
  folders: EnrichedFolder | EnrichedFolder[],
  forceOrOpts?: boolean | { force?: boolean; appendProps?: string[] }
): Promise<void>

Stores folders to IndexedDB. Merges with existing cached data using mergeObjects. Uses bulkPut for multiple folders.

cacheFoldersBatchAsync

cacheFoldersBatchAsync: BatchedFunction
// Batches: 100 items, 10s max retention

Batched version for high-throughput caching.

cachePostAttachment

cachePostAttachment(
  posts: PostReadResponse[],
  folders: MoodReadResponse[],
  relName?: string | string[]  // Default: "attachment"
): Promise<void>

Creates edges for post-folder relationships. Uses delayed edge storage.

cacheGrants

cacheGrants(
  folderId: string,
  grants: MoodListGranteesResponse
): Promise<void>

Caches grantee users and their access edges with level and updated timestamp.

Edge Table Reference

PatternRelationship
folder+{id}+attachment+postPost attached to folder
folder+{id}+access+userUser has access to folder
user+{id}+author+folderUser authored folder

URL Construction

Folder detail:/api/mood/{ id }
Folder posts:/api/mood/{ id }/attachments?page=0&sortBy=created&order=desc
Grant access:POST /api/mood/access/grant
Request access:POST /api/mood/access/request
Top folders:/api/mood/top?page=0
One-on-one:/api/mood/oneonone/{ userId }

Naming: moodId vs folderId

Historical context: In the API, folders are called "moods" (legacy naming). The client-side code uses both terms interchangeably:

  • moodId — Used in API responses/requests
  • folderId — Used in some client functions
  • MoodReadResponse vs EnrichedFolder — Same structure