padloc/packages/core/src/collection.ts

112 lines
3.2 KiB
TypeScript

import { Serializable } from "./encoding";
import { VaultItem } from "./item";
/**
* A collection of vault items items, used for consolidating changes made independently
* across multiple instances through "merging".
*/
export class VaultItemCollection extends Serializable implements Iterable<VaultItem> {
/** Number of items in this VaultItemCollection */
get size() {
return this._items.size;
}
get hasChanges() {
return !!this._changes.size;
}
/** Aggregated list of tags assigned to the items in this collection */
get tags(): string[] {
const tags = new Set<string>();
for (const r of this) {
for (const t of r.tags) {
tags.add(t);
}
}
return [...tags];
}
private _items: Map<string, VaultItem>;
private _changes = new Map<string, Date>();
constructor(items: VaultItem[] = []) {
super();
this._items = new Map(items.map((item) => [item.id, item] as [string, VaultItem]));
}
/** Get an item with a given `id` */
get(id: string) {
return this._items.get(id) || null;
}
/**
* Updates one or more items based on their id. If no item with the same id
* exists, the item will be added to the collection
*/
update(...items: VaultItem[]) {
for (const item of items) {
item.updated = new Date();
this._items.set(item.id, item);
this._changes.set(item.id, item.updated);
}
}
/**
* Removes one or more items based on their id.
*/
remove(...items: VaultItem[]) {
for (const item of items) {
this._items.delete(item.id);
this._changes.set(item.id, new Date());
}
}
/**
* Merges in changes from another [[VaultItemCollection]] instance.
*/
merge(coll: VaultItemCollection) {
// Delete any items from this collection that don't
// exist in the other collection and haven't been changed recently
for (const item of this) {
if (!this._changes.has(item.id) && !coll.get(item.id)) {
this._items.delete(item.id);
}
}
// Get changes items from other collection (but only if they haven't recently changed locally)
for (const item of coll) {
if (!this._changes.has(item.id)) {
this._items.set(item.id, item);
}
}
}
clearChanges(before?: Date) {
for (const [id, changed] of this._changes.entries()) {
if (!before || changed <= before) {
this._changes.delete(id);
}
}
}
protected _toRaw(version: string) {
return {
items: Array.from(this).map((item) => item.toRaw(version)),
changes: [...this._changes],
};
}
protected _fromRaw({ items, changes }: any) {
this._items = new Map(
items.map((item: any) => [item.id, new VaultItem().fromRaw(item)] as [string, VaultItem])
);
this._changes = new Map<string, Date>(
changes && changes.map(([id, date]: [string, string]) => [id, new Date(date)])
);
}
[Symbol.iterator]() {
return this._items.values();
}
}