History Patterns
Jazz's automatic history tracking enables powerful patterns for building collaborative features. Here's how to implement common history-based functionality.
Audit Logs
Build a complete audit trail showing all changes to your data:
functiongetAuditLog(function getAuditLog(task: Task): { field: string; value: string | undefined; by: Account | null; at: Date; }[]task:task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapTask) { consttype Task = { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapconst changes: any[]changes = []; // Collect edits for all fields constconst fields: string[]fields =var Object: ObjectConstructorProvides functionality common to all JavaScript objects.Object.ObjectConstructor.keys(o: {}): string[] (+1 overload)Returns the names of the enumerable string properties and methods of an object.keys(task); consttask: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapedits =const edits: CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>task.task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapCoMap.$jazz: CoMapJazzApi<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>Jazz methods for CoMaps are inside this property. This allows CoMaps to be used as plain objects while still having access to Jazz methods, and also doesn't limit which key names can be used inside CoMaps.$jazz.CoMapJazzApi<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>.getEdits(): CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>Get the edits made to the CoMap.getEdits(); for (constconst field: stringfield ofconst fields: string[]fields) { constconst editField: "title" | "status"editField =const field: stringfield as keyof typeofedits; if (!const edits: CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>edits[const edits: CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>const editField: "title" | "status"editField]) continue; for (constconst edit: CoMapEdit<string> | CoMapEdit<"todo" | "in-progress" | "completed">edit ofedits[const edits: CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>const editField: "title" | "status"editField].all: CoMapEdit<string>[] | CoMapEdit<"todo" | "in-progress" | "completed">[]all) {const changes: any[]changes.Array<any>.push(...items: any[]): numberAppends new elements to the end of an array, and returns the new length of the array.push({field: stringfield,value: string | undefinedvalue:const edit: CoMapEdit<string> | CoMapEdit<"todo" | "in-progress" | "completed">edit.value?: string | undefinedvalue,by: Account | nullby:const edit: CoMapEdit<string> | CoMapEdit<"todo" | "in-progress" | "completed">edit.by: Account | nullby,at: Dateat:const edit: CoMapEdit<string> | CoMapEdit<"todo" | "in-progress" | "completed">edit.madeAt: DatemadeAt, }); } } // Sort by timestamp (newest first) returnchanges.const changes: { field: string; value: string | undefined; by: Account | null; at: Date; }[]Array<{ field: string; value: string | undefined; by: Account | null; at: Date; }>.sort(compareFn?: ((a: { field: string; value: string | undefined; by: Account | null; at: Date; }, b: { field: string; value: string | undefined; by: Account | null; at: Date; }) => number) | undefined): { field: string; value: string | undefined; by: Account | null; at: Date; }[]Sorts an array in place. This method mutates the array and returns a reference to the same array.sort((a,a: { field: string; value: string | undefined; by: Account | null; at: Date; }b) =>b: { field: string; value: string | undefined; by: Account | null; at: Date; }b.b: { field: string; value: string | undefined; by: Account | null; at: Date; }at: Dateat.Date.getTime(): numberReturns the stored time value in milliseconds since midnight, January 1, 1970 UTC.getTime() -a.a: { field: string; value: string | undefined; by: Account | null; at: Date; }at: Dateat.Date.getTime(): numberReturns the stored time value in milliseconds since midnight, January 1, 1970 UTC.getTime()); } // Use it to show change history constauditLog =const auditLog: { field: string; value: string | undefined; by: Account | null; at: Date; }[]getAuditLog(function getAuditLog(task: Task): { field: string; value: string | undefined; by: Account | null; at: Date; }[]task);const task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapauditLog.const auditLog: { field: string; value: string | undefined; by: Account | null; at: Date; }[]Array<{ field: string; value: string | undefined; by: Account | null; at: Date; }>.forEach(callbackfn: (value: { field: string; value: string | undefined; by: Account | null; at: Date; }, index: number, array: { field: string; value: string | undefined; by: Account | null; at: Date; }[]) => void, thisArg?: any): voidPerforms the specified action for each element in an array.forEach((entry) => { constentry: { field: string; value: string | undefined; by: Account | null; at: Date; }const when: stringwhen =entry.entry: { field: string; value: string | undefined; by: Account | null; at: Date; }at: Dateat.Date.toLocaleString(locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions): string (+2 overloads)Converts a date and time to a string by using the current or specified locale.toLocaleString(); constconst who: string | undefinedwho =entry.entry: { field: string; value: string | undefined; by: Account | null; at: Date; }by: Account | nullby?.Account.profile: Profile | null | undefinedprofile?.Profile.name: string | undefinedname; constconst what: stringwhat =entry.entry: { field: string; value: string | undefined; by: Account | null; at: Date; }field: stringfield; constconst value: string | undefinedvalue =entry.entry: { field: string; value: string | undefined; by: Account | null; at: Date; }value: string | undefinedvalue;var console: ConsoleThe `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(`${const when: stringwhen} - ${const who: string | undefinedwho} changed ${const what: stringwhat} to "${const value: string | undefinedvalue}"`); // 22/05/2025, 12:00:00 - Alice changed title to "New task" });
Activity Feeds
Show recent activity across your application:
functiongetRecentActivity(function getRecentActivity(projects: Project[], since: Date): { project: string; field: string; value: string | undefined; by: Account | null; at: Date; }[]projects:projects: ({ readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap)[]Project[],type Project = { readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapsince: Datesince: Date) { constconst activity: any[]activity = []; for (constproject ofconst project: { readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapprojects) { // Get all fields that might have edits constprojects: ({ readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap)[]const fields: string[]fields =var Object: ObjectConstructorProvides functionality common to all JavaScript objects.Object.ObjectConstructor.keys(o: {}): string[] (+1 overload)Returns the names of the enumerable string properties and methods of an object.keys(project); // Check each field for edit history constconst project: { readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapedits =const edits: CoMapEdits<{ readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>project.const project: { readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapCoMap.$jazz: CoMapJazzApi<{ readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>Jazz methods for CoMaps are inside this property. This allows CoMaps to be used as plain objects while still having access to Jazz methods, and also doesn't limit which key names can be used inside CoMaps.$jazz.CoMapJazzApi<{ readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>.getEdits(): CoMapEdits<{ readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>Get the edits made to the CoMap.getEdits(); for (constconst field: stringfield ofconst fields: string[]fields) { constconst editField: "name" | "status"editField =const field: stringfield as keyof typeofedits; // Skip if no edits exist for this field if (!const edits: CoMapEdits<{ readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>edits[const edits: CoMapEdits<{ readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>const editField: "name" | "status"editField]) continue; for (constconst edit: CoMapEdit<string> | CoMapEdit<"todo" | "in-progress" | "completed">edit ofedits[const edits: CoMapEdits<{ readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>const editField: "name" | "status"editField].all: CoMapEdit<string>[] | CoMapEdit<"todo" | "in-progress" | "completed">[]all) { // Only include edits made after the 'since' date if (const edit: CoMapEdit<string> | CoMapEdit<"todo" | "in-progress" | "completed">edit.madeAt: DatemadeAt >since: Datesince) {const activity: any[]activity.Array<any>.push(...items: any[]): numberAppends new elements to the end of an array, and returns the new length of the array.push({project: stringproject:project.const project: { readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapname: stringname,field: stringfield,value: string | undefinedvalue:const edit: CoMapEdit<string> | CoMapEdit<"todo" | "in-progress" | "completed">edit.value?: string | undefinedvalue,by: Account | nullby:const edit: CoMapEdit<string> | CoMapEdit<"todo" | "in-progress" | "completed">edit.by: Account | nullby,at: Dateat:const edit: CoMapEdit<string> | CoMapEdit<"todo" | "in-progress" | "completed">edit.madeAt: DatemadeAt }); } } } } returnactivity.const activity: { project: string; field: string; value: string | undefined; by: Account | null; at: Date; }[]Array<{ project: string; field: string; value: string | undefined; by: Account | null; at: Date; }>.sort(compareFn?: ((a: { project: string; field: string; value: string | undefined; by: Account | null; at: Date; }, b: { project: string; field: string; value: string | undefined; by: Account | null; at: Date; }) => number) | undefined): { project: string; field: string; value: string | undefined; by: Account | null; at: Date; }[]Sorts an array in place. This method mutates the array and returns a reference to the same array.sort((a,a: { project: string; field: string; value: string | undefined; by: Account | null; at: Date; }b) =>b: { project: string; field: string; value: string | undefined; by: Account | null; at: Date; }b.b: { project: string; field: string; value: string | undefined; by: Account | null; at: Date; }at: Dateat.Date.getTime(): numberReturns the stored time value in milliseconds since midnight, January 1, 1970 UTC.getTime() -a.a: { project: string; field: string; value: string | undefined; by: Account | null; at: Date; }at: Dateat.Date.getTime(): numberReturns the stored time value in milliseconds since midnight, January 1, 1970 UTC.getTime()); } // Show activity from the last hour constconst hourAgo: DatehourAgo = newDate(var Date: DateConstructor new (value: number | string | Date) => Date (+4 overloads)var Date: DateConstructorEnables basic storage and retrieval of dates and times.Date.DateConstructor.now(): numberReturns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).now() - 60 * 60 * 1000); constrecentActivity =const recentActivity: { project: string; field: string; value: string | undefined; by: Account | null; at: Date; }[]getRecentActivity(function getRecentActivity(projects: Project[], since: Date): { project: string; field: string; value: string | undefined; by: Account | null; at: Date; }[]myProjects,const myProjects: ({ readonly name: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap)[]const hourAgo: DatehourAgo); // [{ // project: "New project", // field: "name", // value: "New project", // by: Account, // at: Date // }]
Change Indicators
Show when something was last updated:
functiongetLastUpdated(function getLastUpdated(task: Task): { updatedBy: any; updatedAt: any; message: string; } | nulltask:task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapTask) { // Find the most recent edit across all fields lettype Task = { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMaplet lastEdit: anylastEdit: any = null; constedits =const edits: CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>task.task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapCoMap.$jazz: CoMapJazzApi<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>Jazz methods for CoMaps are inside this property. This allows CoMaps to be used as plain objects while still having access to Jazz methods, and also doesn't limit which key names can be used inside CoMaps.$jazz.CoMapJazzApi<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>.getEdits(): CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>Get the edits made to the CoMap.getEdits(); for (constconst field: stringfield ofvar Object: ObjectConstructorProvides functionality common to all JavaScript objects.Object.ObjectConstructor.keys(o: {}): string[] (+1 overload)Returns the names of the enumerable string properties and methods of an object.keys(task)) { consttask: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapconst editField: "title" | "status"editField =const field: stringfield as keyof typeofedits; // Skip if no edits exist for this field if (!const edits: CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>edits[const edits: CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>const editField: "title" | "status"editField]) continue; constconst fieldEdit: LastAndAllCoMapEdits<string> | LastAndAllCoMapEdits<"todo" | "in-progress" | "completed">fieldEdit =edits[const edits: CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>const editField: "title" | "status"editField]; if (const fieldEdit: LastAndAllCoMapEdits<string> | LastAndAllCoMapEdits<"todo" | "in-progress" | "completed">fieldEdit && (!let lastEdit: anylastEdit ||const fieldEdit: LastAndAllCoMapEdits<string> | LastAndAllCoMapEdits<"todo" | "in-progress" | "completed">fieldEdit.madeAt: DatemadeAt >let lastEdit: anylastEdit.madeAt)) {let lastEdit: anylastEdit =const fieldEdit: LastAndAllCoMapEdits<string> | LastAndAllCoMapEdits<"todo" | "in-progress" | "completed">fieldEdit; } } if (!let lastEdit: anylastEdit) return null; return {updatedBy: anyupdatedBy:let lastEdit: anylastEdit.by?.profile?.name,updatedAt: anyupdatedAt:let lastEdit: anylastEdit.madeAt,message: stringmessage: `Last updated by ${let lastEdit: anylastEdit.by?.profile?.name} at ${let lastEdit: anylastEdit.madeAt.toLocaleString()}` }; } constlastUpdated =const lastUpdated: { updatedBy: any; updatedAt: any; message: string; } | nullgetLastUpdated(function getLastUpdated(task: Task): { updatedBy: any; updatedAt: any; message: string; } | nulltask);const task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapvar console: ConsoleThe `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(lastUpdated?.const lastUpdated: { updatedBy: any; updatedAt: any; message: string; } | nullmessage: string | undefinedmessage); // "Last updated by Alice at 22/05/2025, 12:00:00"
Finding Specific Changes
Query history for specific events:
// Find when a task was completed functionfunction findCompletionTime(task: Task): Date | nullfindCompletionTime(task:task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapTask): Date | null { consttype Task = { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapconst statusEdits: LastAndAllCoMapEdits<"todo" | "in-progress" | "completed"> | undefinedstatusEdits =task.task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapCoMap.$jazz: CoMapJazzApi<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>Jazz methods for CoMaps are inside this property. This allows CoMaps to be used as plain objects while still having access to Jazz methods, and also doesn't limit which key names can be used inside CoMaps.$jazz.CoMapJazzApi<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>.getEdits(): CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>Get the edits made to the CoMap.getEdits().status?: LastAndAllCoMapEdits<"todo" | "in-progress" | "completed"> | undefinedstatus; if (!const statusEdits: LastAndAllCoMapEdits<"todo" | "in-progress" | "completed"> | undefinedstatusEdits) return null; // find() returns the FIRST completion time // If status toggles (completed → in-progress → completed), // this gives you the earliest completion, not the latest constconst completionEdit: CoMapEdit<"todo" | "in-progress" | "completed"> | undefinedcompletionEdit =const statusEdits: LastAndAllCoMapEdits<"todo" | "in-progress" | "completed">statusEdits.all: CoMapEdit<"todo" | "in-progress" | "completed">[]all.Array<CoMapEdit<"todo" | "in-progress" | "completed">>.find(predicate: (value: CoMapEdit<"todo" | "in-progress" | "completed">, index: number, obj: CoMapEdit<"todo" | "in-progress" | "completed">[]) => unknown, thisArg?: any): CoMapEdit<...> | undefined (+1 overload)Returns the value of the first element in the array where predicate is true, and undefined otherwise.find(edit: CoMapEdit<"todo" | "in-progress" | "completed">edit =>edit: CoMapEdit<"todo" | "in-progress" | "completed">edit.value?: "todo" | "in-progress" | "completed" | undefinedvalue === "completed" ); returnconst completionEdit: CoMapEdit<"todo" | "in-progress" | "completed"> | undefinedcompletionEdit?.madeAt: Date | undefinedmadeAt || null; } // To get the LATEST completion time instead reverse the array, then find: functionfunction findLatestCompletionTime(task: Task): Date | nullfindLatestCompletionTime(task:task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapTask): Date | null { consttype Task = { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapconst statusEdits: LastAndAllCoMapEdits<"todo" | "in-progress" | "completed"> | undefinedstatusEdits =task.task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapCoMap.$jazz: CoMapJazzApi<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>Jazz methods for CoMaps are inside this property. This allows CoMaps to be used as plain objects while still having access to Jazz methods, and also doesn't limit which key names can be used inside CoMaps.$jazz.CoMapJazzApi<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>.getEdits(): CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>Get the edits made to the CoMap.getEdits().status?: LastAndAllCoMapEdits<"todo" | "in-progress" | "completed"> | undefinedstatus; if (!const statusEdits: LastAndAllCoMapEdits<"todo" | "in-progress" | "completed"> | undefinedstatusEdits) return null; // Reverse and find (stops at first match) constconst latestCompletionEdit: CoMapEdit<"todo" | "in-progress" | "completed"> | undefinedlatestCompletionEdit =const statusEdits: LastAndAllCoMapEdits<"todo" | "in-progress" | "completed">statusEdits.all: CoMapEdit<"todo" | "in-progress" | "completed">[]all .Array<CoMapEdit<"todo" | "in-progress" | "completed">>.slice(start?: number, end?: number): CoMapEdit<"todo" | "in-progress" | "completed">[]Returns a copy of a section of an array. For both start and end, a negative index can be used to indicate an offset from the end of the array. For example, -2 refers to the second to last element of the array.slice() // Create copy to avoid mutating original .Array<CoMapEdit<"todo" | "in-progress" | "completed">>.reverse(): CoMapEdit<"todo" | "in-progress" | "completed">[]Reverses the elements in an array in place. This method mutates the array and returns a reference to the same array.reverse() .Array<CoMapEdit<"todo" | "in-progress" | "completed">>.find(predicate: (value: CoMapEdit<"todo" | "in-progress" | "completed">, index: number, obj: CoMapEdit<"todo" | "in-progress" | "completed">[]) => unknown, thisArg?: any): CoMapEdit<...> | undefined (+1 overload)Returns the value of the first element in the array where predicate is true, and undefined otherwise.find(edit: CoMapEdit<"todo" | "in-progress" | "completed">edit =>edit: CoMapEdit<"todo" | "in-progress" | "completed">edit.value?: "todo" | "in-progress" | "completed" | undefinedvalue === "completed"); returnconst latestCompletionEdit: CoMapEdit<"todo" | "in-progress" | "completed"> | undefinedlatestCompletionEdit?.madeAt: Date | undefinedmadeAt || null; }var console: ConsoleThe `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(function findCompletionTime(task: Task): Date | nullfindCompletionTime(task)); // First completionconst task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapvar console: ConsoleThe `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(function findLatestCompletionTime(task: Task): Date | nullfindLatestCompletionTime(task)); // Most recent completion // Find who made a specific change functionconst task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapfunction findWhoChanged(task: Task, field: string, value: any): Account | nullfindWhoChanged(task:task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapTask,type Task = { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapfield: stringfield: string,value: anyvalue: any) { consttaskEdits =const taskEdits: CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>task.task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapCoMap.$jazz: CoMapJazzApi<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>Jazz methods for CoMaps are inside this property. This allows CoMaps to be used as plain objects while still having access to Jazz methods, and also doesn't limit which key names can be used inside CoMaps.$jazz.CoMapJazzApi<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>.getEdits(): CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>Get the edits made to the CoMap.getEdits(); constconst fieldEdits: LastAndAllCoMapEdits<"todo" | "in-progress" | "completed"> | LastAndAllCoMapEdits<string> | undefinedfieldEdits =taskEdits[const taskEdits: CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>field: stringfield as keyof typeoftaskEdits]; if (!const taskEdits: CoMapEdits<{ readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMap>const fieldEdits: LastAndAllCoMapEdits<"todo" | "in-progress" | "completed"> | LastAndAllCoMapEdits<string> | undefinedfieldEdits) return null; constconst matchingEdit: CoMapEdit<string> | undefinedmatchingEdit =const fieldEdits: LastAndAllCoMapEdits<"todo" | "in-progress" | "completed"> | LastAndAllCoMapEdits<string>fieldEdits.all: CoMapEdit<"todo" | "in-progress" | "completed">[] | CoMapEdit<string>[]all.Array<T>.find(predicate: (value: CoMapEdit<string>, index: number, obj: CoMapEdit<string>[]) => unknown, thisArg?: any): CoMapEdit<string> | undefinedReturns the value of the first element in the array where predicate is true, and undefined otherwise.find(edit: CoMapEdit<string>edit =>edit: CoMapEdit<string>edit.value?: string | undefinedvalue ===value: anyvalue); returnconst matchingEdit: CoMapEdit<string> | undefinedmatchingEdit?.by: Account | null | undefinedby || null; } constconst account: Account | nullaccount =function findWhoChanged(task: Task, field: string, value: any): Account | nullfindWhoChanged(task, "status", "completed");const task: { readonly title: string; readonly status: "todo" | "in-progress" | "completed"; } & CoMapvar console: ConsoleThe `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(const account: Account | nullaccount?.Account.profile: Profile | null | undefinedprofile?.Profile.name: string | undefinedname); // Alice
Further Reading
- History - Complete reference for the history API
- Subscription & Loading - Ensure CoValues are loaded before accessing history