@tldraw/store
Version:
tldraw infinite canvas SDK (store).
361 lines (360 loc) • 13.3 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var StoreQueries_exports = {};
__export(StoreQueries_exports, {
StoreQueries: () => StoreQueries
});
module.exports = __toCommonJS(StoreQueries_exports);
var import_state = require("@tldraw/state");
var import_utils = require("@tldraw/utils");
var import_executeQuery = require("./executeQuery");
var import_IncrementalSetConstructor = require("./IncrementalSetConstructor");
var import_setUtils = require("./setUtils");
class StoreQueries {
constructor(recordMap, history) {
this.recordMap = recordMap;
this.history = history;
}
/**
* A cache of derivations (indexes).
*
* @internal
*/
indexCache = /* @__PURE__ */ new Map();
/**
* A cache of derivations (filtered histories).
*
* @internal
*/
historyCache = /* @__PURE__ */ new Map();
/**
* Create a derivation that contains the history for a given type
*
* @param typeName - The name of the type to filter by.
* @returns A derivation that returns the ids of all records of the given type.
* @public
*/
filterHistory(typeName) {
if (this.historyCache.has(typeName)) {
return this.historyCache.get(typeName);
}
const filtered = (0, import_state.computed)(
"filterHistory:" + typeName,
(lastValue, lastComputedEpoch) => {
if ((0, import_state.isUninitialized)(lastValue)) {
return this.history.get();
}
const diff = this.history.getDiffSince(lastComputedEpoch);
if (diff === import_state.RESET_VALUE) return this.history.get();
const res = { added: {}, removed: {}, updated: {} };
let numAdded = 0;
let numRemoved = 0;
let numUpdated = 0;
for (const changes of diff) {
for (const added of (0, import_utils.objectMapValues)(changes.added)) {
if (added.typeName === typeName) {
if (res.removed[added.id]) {
const original = res.removed[added.id];
delete res.removed[added.id];
numRemoved--;
if (original !== added) {
res.updated[added.id] = [original, added];
numUpdated++;
}
} else {
res.added[added.id] = added;
numAdded++;
}
}
}
for (const [from, to] of (0, import_utils.objectMapValues)(changes.updated)) {
if (to.typeName === typeName) {
if (res.added[to.id]) {
res.added[to.id] = to;
} else if (res.updated[to.id]) {
res.updated[to.id] = [res.updated[to.id][0], to];
} else {
res.updated[to.id] = [from, to];
numUpdated++;
}
}
}
for (const removed of (0, import_utils.objectMapValues)(changes.removed)) {
if (removed.typeName === typeName) {
if (res.added[removed.id]) {
delete res.added[removed.id];
numAdded--;
} else if (res.updated[removed.id]) {
res.removed[removed.id] = res.updated[removed.id][0];
delete res.updated[removed.id];
numUpdated--;
numRemoved++;
} else {
res.removed[removed.id] = removed;
numRemoved++;
}
}
}
}
if (numAdded || numRemoved || numUpdated) {
return (0, import_state.withDiff)(this.history.get(), res);
} else {
return lastValue;
}
},
{ historyLength: 100 }
);
this.historyCache.set(typeName, filtered);
return filtered;
}
/**
* Create a derivation that returns an index on a property for the given type.
*
* @param typeName - The name of the type.
* @param property - The name of the property.
* @public
*/
index(typeName, property) {
const cacheKey = typeName + ":" + property;
if (this.indexCache.has(cacheKey)) {
return this.indexCache.get(cacheKey);
}
const index = this.__uncached_createIndex(typeName, property);
this.indexCache.set(cacheKey, index);
return index;
}
/**
* Create a derivation that returns an index on a property for the given type.
*
* @param typeName - The name of the type?.
* @param property - The name of the property?.
* @internal
*/
__uncached_createIndex(typeName, property) {
const typeHistory = this.filterHistory(typeName);
const fromScratch = () => {
typeHistory.get();
const res = /* @__PURE__ */ new Map();
for (const record of this.recordMap.values()) {
if (record.typeName === typeName) {
const value = record[property];
if (!res.has(value)) {
res.set(value, /* @__PURE__ */ new Set());
}
res.get(value).add(record.id);
}
}
return res;
};
return (0, import_state.computed)(
"index:" + typeName + ":" + property,
(prevValue, lastComputedEpoch) => {
if ((0, import_state.isUninitialized)(prevValue)) return fromScratch();
const history = typeHistory.getDiffSince(lastComputedEpoch);
if (history === import_state.RESET_VALUE) {
return fromScratch();
}
const setConstructors = /* @__PURE__ */ new Map();
const add = (value, id) => {
let setConstructor = setConstructors.get(value);
if (!setConstructor)
setConstructor = new import_IncrementalSetConstructor.IncrementalSetConstructor(
prevValue.get(value) ?? /* @__PURE__ */ new Set()
);
setConstructor.add(id);
setConstructors.set(value, setConstructor);
};
const remove = (value, id) => {
let set = setConstructors.get(value);
if (!set) set = new import_IncrementalSetConstructor.IncrementalSetConstructor(prevValue.get(value) ?? /* @__PURE__ */ new Set());
set.remove(id);
setConstructors.set(value, set);
};
for (const changes of history) {
for (const record of (0, import_utils.objectMapValues)(changes.added)) {
if (record.typeName === typeName) {
const value = record[property];
add(value, record.id);
}
}
for (const [from, to] of (0, import_utils.objectMapValues)(changes.updated)) {
if (to.typeName === typeName) {
const prev = from[property];
const next = to[property];
if (prev !== next) {
remove(prev, to.id);
add(next, to.id);
}
}
}
for (const record of (0, import_utils.objectMapValues)(changes.removed)) {
if (record.typeName === typeName) {
const value = record[property];
remove(value, record.id);
}
}
}
let nextValue = void 0;
let nextDiff = void 0;
for (const [value, setConstructor] of setConstructors) {
const result = setConstructor.get();
if (!result) continue;
if (!nextValue) nextValue = new Map(prevValue);
if (!nextDiff) nextDiff = /* @__PURE__ */ new Map();
if (result.value.size === 0) {
nextValue.delete(value);
} else {
nextValue.set(value, result.value);
}
nextDiff.set(value, result.diff);
}
if (nextValue && nextDiff) {
return (0, import_state.withDiff)(nextValue, nextDiff);
}
return prevValue;
},
{ historyLength: 100 }
);
}
/**
* Create a derivation that will return a signle record matching the given query.
*
* It will return undefined if there is no matching record
*
* @param typeName - The name of the type?
* @param queryCreator - A function that returns the query expression.
* @param name - (optional) The name of the query.
*/
record(typeName, queryCreator = () => ({}), name = "record:" + typeName + (queryCreator ? ":" + queryCreator.toString() : "")) {
const ids = this.ids(typeName, queryCreator, name);
return (0, import_state.computed)(name, () => {
for (const id of ids.get()) {
return this.recordMap.get(id);
}
return void 0;
});
}
/**
* Create a derivation that will return an array of records matching the given query
*
* @param typeName - The name of the type?
* @param queryCreator - A function that returns the query expression.
* @param name - (optinal) The name of the query.
*/
records(typeName, queryCreator = () => ({}), name = "records:" + typeName + (queryCreator ? ":" + queryCreator.toString() : "")) {
const ids = this.ids(typeName, queryCreator, "ids:" + name);
return (0, import_state.computed)(
name,
() => {
return Array.from(ids.get(), (id) => this.recordMap.get(id));
},
{
isEqual: import_utils.areArraysShallowEqual
}
);
}
/**
* Create a derivation that will return the ids of all records of the given type.
*
* @param typeName - The name of the type.
* @param queryCreator - A function that returns the query expression.
* @param name - (optinal) The name of the query.
*/
ids(typeName, queryCreator = () => ({}), name = "ids:" + typeName + (queryCreator ? ":" + queryCreator.toString() : "")) {
const typeHistory = this.filterHistory(typeName);
const fromScratch = () => {
typeHistory.get();
const query = queryCreator();
if (Object.keys(query).length === 0) {
const ids = /* @__PURE__ */ new Set();
for (const record of this.recordMap.values()) {
if (record.typeName === typeName) ids.add(record.id);
}
return ids;
}
return (0, import_executeQuery.executeQuery)(this, typeName, query);
};
const fromScratchWithDiff = (prevValue) => {
const nextValue = fromScratch();
const diff = (0, import_setUtils.diffSets)(prevValue, nextValue);
if (diff) {
return (0, import_state.withDiff)(nextValue, diff);
} else {
return prevValue;
}
};
const cachedQuery = (0, import_state.computed)("ids_query:" + name, queryCreator, {
isEqual: import_utils.isEqual
});
return (0, import_state.computed)(
"query:" + name,
(prevValue, lastComputedEpoch) => {
const query = cachedQuery.get();
if ((0, import_state.isUninitialized)(prevValue)) {
return fromScratch();
}
if (lastComputedEpoch < cachedQuery.lastChangedEpoch) {
return fromScratchWithDiff(prevValue);
}
const history = typeHistory.getDiffSince(lastComputedEpoch);
if (history === import_state.RESET_VALUE) {
return fromScratchWithDiff(prevValue);
}
const setConstructor = new import_IncrementalSetConstructor.IncrementalSetConstructor(
prevValue
);
for (const changes of history) {
for (const added of (0, import_utils.objectMapValues)(changes.added)) {
if (added.typeName === typeName && (0, import_executeQuery.objectMatchesQuery)(query, added)) {
setConstructor.add(added.id);
}
}
for (const [_, updated] of (0, import_utils.objectMapValues)(changes.updated)) {
if (updated.typeName === typeName) {
if ((0, import_executeQuery.objectMatchesQuery)(query, updated)) {
setConstructor.add(updated.id);
} else {
setConstructor.remove(updated.id);
}
}
}
for (const removed of (0, import_utils.objectMapValues)(changes.removed)) {
if (removed.typeName === typeName) {
setConstructor.remove(removed.id);
}
}
}
const result = setConstructor.get();
if (!result) {
return prevValue;
}
return (0, import_state.withDiff)(result.value, result.diff);
},
{ historyLength: 50 }
);
}
exec(typeName, query) {
const ids = (0, import_executeQuery.executeQuery)(this, typeName, query);
if (ids.size === 0) {
return import_state.EMPTY_ARRAY;
}
return Array.from(ids, (id) => this.recordMap.get(id));
}
}
//# sourceMappingURL=StoreQueries.js.map