CoMaps
CoMaps are key-value objects that work like JavaScript objects. You can access properties with dot notation and define typed fields that provide TypeScript safety. They're ideal for structured data that needs type validation.
Creating CoMaps
CoMaps are typically defined by extending the CoMap
class and specifying primitive fields using the co
declarer (see Defining schemas: CoValues for more details on primitive fields):
class Project extends CoMap { name = co.string; startDate = co.Date; status = co.literal("planning", "active", "completed"); coordinator = co.optional.ref(Member); }
You can create either struct-like CoMaps with fixed fields (as above) or record-like CoMaps for key-value pairs:
class Inventory extends CoMap.Record(co.number) {}
To instantiate a CoMap:
const project = Project.create({ name: "Spring Planting", startDate: new Date("2025-03-15"), status: "planning", }); const inventory = Inventory.create({ tomatoes: 48, basil: 12, });
Ownership
When creating CoMaps, you can specify ownership to control access:
// Create with default owner (current user) const
const privateProject: Project
privateProject =class Project
Project.
CoMap.create<Project>(this: CoValueClass<...>, init: { name: co<string> & (co<string> | undefined); startDate: co<Date> & (co<Date> | undefined); status: co<"planning" | "active" | "completed"> & (co<...> | undefined); coordinator?: Member | ... 2 more ... | undefined; }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group): Project
Create a new CoMap with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoMap will immediately be persisted and synced to connected peers.create({name: co<string> & (co<string> | undefined)
name: "My Herb Garden",startDate: co<Date> & (co<Date> | undefined)
startDate: newDate("2025-04-01"),
var Date: DateConstructor new (value: number | string | Date) => Date (+4 overloads)
status: co<"planning" | "active" | "completed"> & (co<"planning" | "active" | "completed"> | undefined)
status: "planning", }); // Create with shared ownership constconst gardenGroup: Group
gardenGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const gardenGroup: Group
gardenGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(const memberAccount: Account
memberAccount, "writer"); constconst communityProject: Project
communityProject =class Project
Project.
CoMap.create<Project>(this: CoValueClass<...>, init: { name: co<string> & (co<string> | undefined); startDate: co<Date> & (co<Date> | undefined); status: co<"planning" | "active" | "completed"> & (co<...> | undefined); coordinator?: Member | ... 2 more ... | undefined; }, options?: { owner: Account | Group; unique?: CoValueUniqueness["uniqueness"]; } | Account | Group): Project
Create a new CoMap with the given initial values and owner. The owner (a Group or Account) determines access rights to the CoMap. The CoMap will immediately be persisted and synced to connected peers.create( {name: co<string> & (co<string> | undefined)
name: "Community Vegetable Plot",startDate: co<Date> & (co<Date> | undefined)
startDate: newDate("2025-03-20"),
var Date: DateConstructor new (value: number | string | Date) => Date (+4 overloads)
status: co<"planning" | "active" | "completed"> & (co<"planning" | "active" | "completed"> | undefined)
status: "planning", }, {owner: Account | Group
owner:const gardenGroup: Group
gardenGroup }, );
See Groups as permission scopes for more information on how to use groups to control access to CoMaps.
Reading from CoMaps
CoMaps can be accessed using familiar JavaScript object notation:
console.log(project.name); // "Spring Planting" console.log(project.status); // "planning"
Handling Optional Fields
Optional fields require checks before access:
if (project.coordinator) { console.log(project.coordinator.name); // Safe access }
Working with Record CoMaps
For record-type CoMaps, you can access values using bracket notation:
const inventory = Inventory.create({ tomatoes: 48, peppers: 24, basil: 12 }); console.log(inventory["tomatoes"]); // 48
Updating CoMaps
Updating CoMap properties uses standard JavaScript assignment:
project.name = "Spring Vegetable Garden"; // Update name project.startDate = new Date("2025-03-20"); // Update date
Type Safety
CoMaps are fully typed in TypeScript, giving you autocomplete and error checking:
project.name = "Spring Vegetable Planting"; // ✓ Valid string project.startDate = "2025-03-15"; // ✗ Type error: expected Date
Deleting Properties
You can delete properties from CoMaps:
delete inventory["basil"]; // Remove a key-value pair // For optional fields in struct-like CoMaps project.coordinator = null; // Remove the reference
Best Practices
Structuring Data
- Use struct-like CoMaps for entities with fixed, known properties
- Use record-like CoMaps for dynamic key-value collections
- Group related properties into nested CoMaps for better organization
Common Patterns
Using Computed Properties
CoMaps support computed properties and methods:
class ComputedProject extends CoMap { name = co.string; startDate = co.Date; endDate = co.optional.Date; get isActive() { const now = new Date(); return now >= this.startDate && (!this.endDate || now <= this.endDate); } formatDuration(format: "short" | "full") { const start = this.startDate.toLocaleDateString(); if (!this.endDate) { return format === "full" ? `Started on ${start}, ongoing` : `From ${start}`; } const end = this.endDate.toLocaleDateString(); return format === "full" ? `From ${start} to ${end}` : `${(this.endDate.getTime() - this.startDate.getTime()) / 86400000} days`; } } // ... console.log(computedProject.isActive); // false console.log(computedProject.formatDuration("short")); // "3 days"