prostgles-client
Version:
Reactive client for Postgres
833 lines (832 loc) • 35.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.quickClone = exports.mergeDeep = exports.SyncedTable = exports.debug = void 0;
const prostgles_types_1 = require("prostgles-types");
const getMultiSyncSubscription_1 = require("./getMultiSyncSubscription");
const DEBUG_KEY = "DEBUG_SYNCEDTABLE";
const hasWnd = typeof window !== "undefined";
const debug = function (...args) {
if (hasWnd && window[DEBUG_KEY]) {
window[DEBUG_KEY](...args);
}
};
exports.debug = debug;
const STORAGE_TYPES = {
map: "map",
localStorage: "localStorage",
// object: "object",
};
class SyncedTable {
/**
* add debug mode to fix sudden no data and sync listeners bug
*/
set multiSubscriptions(mSubs) {
(0, exports.debug)(mSubs, this._multiSubscriptions);
this._multiSubscriptions = mSubs.slice(0);
}
get multiSubscriptions() {
return this._multiSubscriptions;
}
set singleSubscriptions(sSubs) {
(0, exports.debug)(sSubs, this._singleSubscriptions);
this._singleSubscriptions = sSubs.slice(0);
}
get singleSubscriptions() {
return this._singleSubscriptions;
}
constructor({ name, filter, onChange, onReady, onDebug, db, skipFirstTrigger = false, select = "*", storageType = "map", patchText = false, patchJSON = false, onError, }) {
this.throttle = 100;
this.batch_size = 50;
this.skipFirstTrigger = false;
this.columns = [];
this._multiSubscriptions = [];
this._singleSubscriptions = [];
this.itemsMap = new Map();
this.isSynced = false;
/**
* Will update text/json fields through patching method
* This will send less data to server
* @param walData
*/
this.updatePatches = async (walData) => {
var _a, _b;
let remaining = walData.map((d) => d.current);
const patched = [], patchedItems = [];
if (this.columns.length &&
((_a = this.tableHandler) === null || _a === void 0 ? void 0 : _a.updateBatch) &&
(this.patchText || this.patchJSON)) {
// const jCols = this.columns.filter(c => c.data_type === "json")
const txtCols = this.columns.filter((c) => c.data_type === "text");
if (this.patchText && txtCols.length) {
remaining = [];
const id_keys = [this.synced_field, ...this.id_fields];
await Promise.all(walData.slice(0).map(async (d, i) => {
const { current, initial } = { ...d };
let patchedDelta;
if (initial) {
txtCols.map((c) => {
if (!id_keys.includes(c.name) && c.name in current) {
patchedDelta !== null && patchedDelta !== void 0 ? patchedDelta : (patchedDelta = { ...current });
patchedDelta[c.name] = (0, prostgles_types_1.getTextPatch)(initial[c.name], current[c.name]);
}
});
if (patchedDelta && this.wal) {
patchedItems.push(patchedDelta);
patched.push([this.wal.getIdObj(patchedDelta), this.wal.getDeltaObj(patchedDelta)]);
}
}
// console.log("json-stable-stringify ???")
if (!patchedDelta) {
remaining.push(current);
}
}));
}
}
/**
* There is a decent chance the patch update will fail.
* As such, to prevent sync batch update failures, the patched updates are updated separately.
* If patch update fails then sync batch normally without patch.
*/
if (patched.length) {
try {
await ((_b = this.tableHandler) === null || _b === void 0 ? void 0 : _b.updateBatch(patched));
}
catch (e) {
console.log("failed to patch update", e);
remaining = remaining.concat(patchedItems);
}
}
return remaining.filter((d) => d);
};
/**
* Notifies multi subs with ALL data + deltas. Attaches handles on data if required
* @param newData -> updates. Must include id_fields + updates
*/
this._notifySubscribers = (changes = []) => {
var _a, _b;
if (!this.isSynced) {
(_a = this.onDebug) === null || _a === void 0 ? void 0 : _a.call(this, { command: "notifySubscribers", data: [], info: "not synced yet" });
return;
}
else {
(_b = this.onDebug) === null || _b === void 0 ? void 0 : _b.call(this, { command: "notifySubscribers", data: changes });
}
/* Deleted items (changes = []) do not trigger singleSubscriptions notify because it might break things */
const items = [], deltas = [], ids = [];
changes.map(({ idObj, newItem, delta }) => {
/* Single subs do not care about the filter */
this.singleSubscriptions
.filter((s) => this.matchesIdObj(s.idObj, idObj))
.map(async (s) => {
try {
await s.notify(newItem, delta);
}
catch (e) {
console.error("SyncedTable failed to notify: ", e);
}
});
/* Preparing data for multi subs */
if (this.matchesFilter(newItem)) {
items.push(newItem);
deltas.push(delta);
ids.push(idObj);
}
});
if (this.onChange || this.multiSubscriptions.length) {
const allItems = [], allDeltas = [];
this.getItems().map((d) => {
allItems.push({ ...d });
const dIdx = items.findIndex((_d) => this.matchesIdObj(d, _d));
allDeltas.push(deltas[dIdx]);
});
/* Notify main subscription */
if (this.onChange) {
try {
this.onChange(allItems, allDeltas);
}
catch (e) {
console.error("SyncedTable failed to notify onChange: ", e);
}
}
/* Multisubs must not forget about the original filter */
this.multiSubscriptions.map(async (s) => {
try {
await s.notify(allItems, allDeltas);
}
catch (e) {
console.error("SyncedTable failed to notify: ", e);
}
});
}
};
this.unsubscribe = (onChange) => {
this.singleSubscriptions = this.singleSubscriptions.filter((s) => s._onChange !== onChange);
this.multiSubscriptions = this.multiSubscriptions.filter((s) => s._onChange !== onChange);
(0, exports.debug)("unsubscribe", this);
return "ok";
};
this.unsync = () => {
var _a;
(_a = this.dbSync) === null || _a === void 0 ? void 0 : _a.unsync();
};
this.destroy = () => {
this.unsync();
this.multiSubscriptions = [];
this.singleSubscriptions = [];
this.itemsMap.clear();
this.onChange = undefined;
};
this.delete = async (item, from_server = false) => {
var _a;
const idObj = this.getIdObj(item);
this.setItem(idObj, true, true);
if (!from_server && ((_a = this.tableHandler) === null || _a === void 0 ? void 0 : _a.delete)) {
await this.tableHandler.delete(idObj);
}
this._notifySubscribers();
return true;
};
/**
* Ensures that all object keys match valid column names
*/
this.checkItemCols = (item) => {
if (this.columns.length) {
const badCols = Object.keys({ ...item }).filter((k) => !this.columns.find((c) => c.name === k));
if (badCols.length) {
throw `Unexpected columns in sync item update: ` + badCols.join(", ");
}
}
};
/**
* Upserts data locally -> notify subs -> sends to server if required
* synced_field is populated if data is not from server
* @param items <{ idObj: object, delta: object }[]> Data items that changed
* @param from_server : <boolean> If false then updates will be sent to server
*/
this.upsert = async (items, from_server = false) => {
var _a, _b;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if ((!items || !items.length) && !from_server)
throw "No data provided for upsert";
/* If data has been deleted then wait for it to sync with server before continuing */
// if(from_server && this.getDeleted().length){
// await this.syncDeleted();
// }
const results = [];
let status;
const walItems = [];
await Promise.all(items.map(async (item, i) => {
var _a;
// let d = { ...item.idObj, ...item.delta };
const idObj = { ...item.idObj };
let delta = { ...item.delta };
/* Convert undefined to null because:
1) JSON.stringify drops these keys
2) Postgres does not have undefined
*/
Object.keys(delta).map((k) => {
if (delta[k] === undefined)
delta[k] = null;
});
if (!from_server) {
this.checkItemCols({ ...item.delta, ...item.idObj });
}
const oldItem = this.getItem(idObj);
/* Calc delta if missing or if from server */
if ((from_server || (0, prostgles_types_1.isEmpty)(delta)) && !(0, prostgles_types_1.isEmpty)(oldItem)) {
delta = this.getDelta(oldItem || {}, delta);
}
/* Add synced if local update */
/** Will need to check client clock shift */
if (!from_server) {
delta[this.synced_field] = Date.now();
}
let newItem = { ...oldItem, ...delta, ...idObj };
if (oldItem && !from_server) {
/**
* Merge deep
*/
if ((_a = item.opts) === null || _a === void 0 ? void 0 : _a.deepMerge) {
newItem = (0, exports.mergeDeep)({ ...oldItem, ...idObj }, { ...delta });
}
}
/* Update existing -> Expecting delta */
if (oldItem) {
status =
oldItem[this.synced_field] < newItem[this.synced_field] ? "updated" : "unchanged";
/* Insert new item */
}
else {
status = "inserted";
}
this.setItem(newItem);
// if(!status) throw "changeInfo status missing"
const changeInfo = { idObj, delta, oldItem, newItem, status, from_server };
// const idStr = this.getIdStr(idObj);
/* IF Local updates then Keep any existing oldItem to revert to the earliest working item */
if (!from_server) {
/* Patch server data if necessary and update separately to account for errors */
// let updatedWithPatch = false;
// if(this.columns && this.columns.length && (this.patchText || this.patchJSON)){
// // const jCols = this.columns.filter(c => c.data_type === "json")
// const txtCols = this.columns.filter(c => c.data_type === "text");
// if(this.patchText && txtCols.length && this.db[this.name].update){
// let patchedDelta;
// txtCols.map(c => {
// if(c.name in changeInfo.delta){
// patchedDelta = patchedDelta || {
// ...changeInfo.delta,
// }
// patchedDelta[c.name] = getTextPatch(changeInfo.oldItem[c.name], changeInfo.delta[c.name]);
// }
// });
// if(patchedDelta){
// try {
// await this.db[this.name].update(idObj, patchedDelta);
// updatedWithPatch = true;
// } catch(e) {
// console.log("failed to patch update", e)
// }
// }
// // console.log("json-stable-stringify ???")
// }
// }
walItems.push({
initial: oldItem,
current: { ...newItem },
});
}
if (!(0, prostgles_types_1.isEmpty)(changeInfo.delta)) {
results.push(changeInfo);
}
/* TODO: Deletes from server */
// if(allow_deletes){
// items = this.getItems();
// }
return true;
})).catch((err) => {
console.error("SyncedTable failed upsert: ", err);
});
(_a = this.notifyWal) === null || _a === void 0 ? void 0 : _a.addData(results.map((d) => ({ initial: d.oldItem, current: d.newItem })));
/* Push to server */
if (!from_server && walItems.length) {
(_b = this.wal) === null || _b === void 0 ? void 0 : _b.addData(walItems);
}
};
/**
* Sets the current data
*/
this.setItems = (_items) => {
const items = (0, exports.quickClone)(_items);
if (this.storageType === STORAGE_TYPES.localStorage) {
if (!hasWnd)
throw "Cannot access window object. Choose another storage method (array OR object)";
window.localStorage.setItem(this.name, JSON.stringify(items));
}
else if (this.storageType === STORAGE_TYPES.map) {
this.itemsMap = new Map(items.map((item) => {
const id = this.getIdStr(item);
return [id, { ...item }];
}));
}
};
/**
* Returns the current data ordered by synced_field ASC and matching the main filter;
*/
this.getItems = () => {
let items = [];
if (this.storageType === STORAGE_TYPES.localStorage) {
if (!hasWnd)
throw "Cannot access window object. Choose another storage method (array OR object)";
const cachedStr = window.localStorage.getItem(this.name);
if (cachedStr) {
try {
items = JSON.parse(cachedStr);
}
catch (e) {
console.error(e);
}
}
}
else if (this.storageType === STORAGE_TYPES.map) {
items = Array.from(this.itemsMap.values()).map((d) => ({ ...d }));
}
if (this.id_fields.length && this.synced_field) {
const s_fields = [this.synced_field, ...this.id_fields.sort()];
items = items
.filter((d) => {
return !this.filter || !(0, prostgles_types_1.getKeys)(this.filter).find((key) => d[key] !== this.filter[key]);
})
.sort((a, b) => s_fields
.map((key) => (a[key] < b[key] ? -1
: a[key] > b[key] ? 1
: 0))
.find((v) => v));
}
else
throw "id_fields AND/OR synced_field missing";
return (0, exports.quickClone)(items);
};
/**
* Sync data request
*/
this.getBatch = ({ from_synced, to_synced, offset, limit } = { offset: 0, limit: undefined }) => {
const items = this.getItems();
let res = items
.map((c) => ({ ...c }))
.filter((c) => (!Number.isFinite(from_synced) || +c[this.synced_field] >= +from_synced) &&
(!Number.isFinite(to_synced) || +c[this.synced_field] <= +to_synced));
if (offset || limit)
res = res.splice(offset !== null && offset !== void 0 ? offset : 0, limit || res.length);
return res;
};
this.name = name;
this.filter = filter;
this.select = select;
this.onChange = onChange;
if (onDebug) {
this.onDebug = (evt) => onDebug({ ...evt, type: "sync", tableName: name }, this);
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!STORAGE_TYPES[storageType])
throw "Invalid storage type. Expecting one of: " + Object.keys(STORAGE_TYPES).join(", ");
if (!hasWnd && storageType === STORAGE_TYPES.localStorage) {
console.warn("Could not set storageType to localStorage: window object missing\nStorage changed to map");
storageType = "map";
}
this.storageType = storageType;
this.patchText = patchText;
this.patchJSON = patchJSON;
const tableHandler = db[name];
if (!tableHandler)
throw `${name} table not found in db`;
this.db = db;
const { _sync, _syncInfo } = tableHandler;
if (!_sync || !_syncInfo)
throw `${name} table does not support sync`;
const { id_fields, synced_field, throttle = 100, batch_size = 50 } = _syncInfo;
if (!id_fields.length || !synced_field)
throw "id_fields/synced_field missing";
this.id_fields = id_fields;
this.synced_field = synced_field;
this.batch_size = batch_size;
this.throttle = throttle;
this.skipFirstTrigger = skipFirstTrigger;
this.multiSubscriptions = [];
this.singleSubscriptions = [];
this.onError =
onError ||
function (err) {
console.error("Sync internal error: ", err);
};
const onSyncRequest = (syncBatchParams) => {
var _a;
let clientSyncInfo = { c_lr: undefined, c_fr: undefined, c_count: 0 };
const batch = this.getBatch(syncBatchParams);
if (batch.length) {
clientSyncInfo = {
c_fr: this.getRowSyncObj(batch[0]),
c_lr: this.getRowSyncObj(batch[batch.length - 1]),
c_count: batch.length,
};
}
(_a = this.onDebug) === null || _a === void 0 ? void 0 : _a.call(this, { command: "onUpdates", data: { syncBatchParams, batch, clientSyncInfo } });
return clientSyncInfo;
}, onPullRequest = async (syncBatchParams) => {
var _a;
// if(this.getDeleted().length){
// await this.syncDeleted();
// }
const data = this.getBatch(syncBatchParams);
await ((_a = this.onDebug) === null || _a === void 0 ? void 0 : _a.call(this, { command: "onPullRequest", data: { syncBatchParams, data } }));
return { data };
}, onUpdates = async (onUpdatesParams) => {
var _a, _b;
await ((_a = this.onDebug) === null || _a === void 0 ? void 0 : _a.call(this, { command: "onUpdates", data: { onUpdatesParams } }));
if ("err" in onUpdatesParams && onUpdatesParams.err) {
(_b = this.onError) === null || _b === void 0 ? void 0 : _b.call(this, onUpdatesParams.err);
}
else if ("isSynced" in onUpdatesParams && onUpdatesParams.isSynced && !this.isSynced) {
this.isSynced = onUpdatesParams.isSynced;
const items = this.getItems().map((d) => ({ ...d }));
this.setItems([]);
const updateItems = items.map((d) => ({
idObj: this.getIdObj(d),
delta: { ...d },
}));
await this.upsert(updateItems, true);
}
else if ("data" in onUpdatesParams) {
/* Delta left empty so we can prepare it here */
const updateItems = onUpdatesParams.data.map((d) => {
return {
idObj: this.getIdObj(d),
delta: d,
};
});
await this.upsert(updateItems, true);
}
else {
console.error("Unexpected onUpdates");
}
return true;
};
const opts = {
id_fields,
synced_field,
throttle,
};
_sync(filter, { select }, { onSyncRequest, onPullRequest, onUpdates }).then((s) => {
this.dbSync = s;
function confirmExit() {
return "Data may be lost. Are you sure?";
}
/**
* Some syncs can be read only. Any changes are local
*/
this.wal = new prostgles_types_1.WAL({
...opts,
batch_size,
onSendStart: () => {
if (hasWnd)
window.onbeforeunload = confirmExit;
},
onSend: async (data, walData) => {
// if(this.patchText){
// const textCols = this.columns.filter(c => c.data_type.toLowerCase().startsWith("text"));
// data = await Promise.all(data.map(d => {
// const dataTextCols = Object.keys(d).filter(k => textCols.find(tc => tc.name === k));
// if(dataTextCols.length){
// /* Create text patches and update separately */
// dada
// }
// return d;
// }))
// }
const _data = await this.updatePatches(walData);
if (!_data.length)
return [];
return this.dbSync.syncData(data);
}, //, deletedData);,
onSendEnd: () => {
if (hasWnd)
window.onbeforeunload = null;
},
});
this.notifyWal = new prostgles_types_1.WAL({
...opts,
batch_size: Infinity,
throttle: 5,
onSend: async (items, fullItems) => {
this._notifySubscribers(fullItems.map((d) => {
var _a;
return ({
delta: this.getDelta((_a = d.initial) !== null && _a !== void 0 ? _a : {}, d.current),
idObj: this.getIdObj(d.current),
newItem: d.current,
});
}));
},
});
onReady();
});
if (tableHandler.getColumns) {
tableHandler.getColumns().then((cols) => {
this.columns = cols;
});
}
if (this.onChange && !this.skipFirstTrigger) {
setTimeout(this.onChange, 0);
}
(0, exports.debug)(this);
}
static create(opts) {
return new Promise((resolve, reject) => {
try {
const res = new SyncedTable({
...opts,
onReady: () => {
setTimeout(() => {
resolve(res);
}, 0);
},
});
}
catch (err) {
reject(err);
}
});
}
/**
* Returns a sync handler to all records within the SyncedTable instance
* @param onChange change listener <(items: object[], delta: object[]) => any >
* @param handlesOnData If true then $upsert and $unsync handles will be added on each data item. True by default;
*/
sync(onChange, handlesOnData = true) {
const { sub, handles } = getMultiSyncSubscription_1.getMultiSyncSubscription.bind(this)({
onChange: onChange,
handlesOnData,
});
this.multiSubscriptions.push(sub);
if (!this.skipFirstTrigger) {
setTimeout(() => {
const items = this.getItems();
sub.notify(items, items);
}, 0);
}
return Object.freeze({ ...handles });
}
makeSingleSyncHandles(idObj, onChange) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!idObj || !onChange)
throw `syncOne(idObj, onChange) -> MISSING idObj or onChange`;
const handles = {
$get: () => this.getItem(idObj),
$find: (idObject) => this.getItem(idObject),
$unsync: () => {
return this.unsubscribe(onChange);
},
$delete: () => {
return this.delete(idObj);
},
$update: (newData, opts) => {
/* DROPPED SYNC BUG */
if (!this.singleSubscriptions.length && !this.multiSubscriptions.length) {
console.warn("No sync listeners");
(0, exports.debug)("nosync", this._singleSubscriptions, this._multiSubscriptions);
}
this.upsert([{ idObj, delta: newData, opts }]);
},
$cloneSync: (onChange) => this.syncOne(idObj, onChange),
// TODO: add clone sync hook
// $useCloneSync: () => {
// const handles = this.syncOne<T, Full>(idObj, item => {
// setItem()
// });
// return handles.$unsync;
// },
$cloneMultiSync: (onChange) => this.sync(onChange, true),
};
return handles;
}
/**
* Returns a sync handler to a specific record within the SyncedTable instance
* @param idObj object containing the target id_fields properties
* @param onChange change listener <(item: object, delta: object) => any >
* @param handlesOnData If true then $update, $delete and $unsync handles will be added on the data item. True by default;
*/
syncOne(idObj, onChange, handlesOnData = true) {
const handles = this.makeSingleSyncHandles(idObj, onChange);
const sub = {
_onChange: onChange,
idObj,
handlesOnData,
handles,
notify: (data, delta) => {
const newData = { ...data };
if (handlesOnData) {
newData.$get = handles.$get;
newData.$find = handles.$find;
newData.$update = handles.$update;
newData.$delete = handles.$delete;
newData.$unsync = handles.$unsync;
newData.$cloneSync = handles.$cloneSync;
}
return onChange(newData, delta);
},
};
this.singleSubscriptions.push(sub);
setTimeout(() => {
const existingData = handles.$get();
if (existingData) {
sub.notify(existingData, existingData);
}
}, 0);
return Object.freeze({ ...handles });
}
getIdStr(d) {
return this.id_fields
.sort()
.map((key) => `${d[key] || ""}`)
.join(".");
}
getIdObj(d) {
const res = {};
this.id_fields.sort().map((key) => {
res[key] = d[key];
});
return res;
}
getRowSyncObj(d) {
const res = {};
[this.synced_field, ...this.id_fields].sort().map((key) => {
res[key] = d[key];
});
return res;
}
matchesFilter(item) {
return Boolean(item &&
(!this.filter ||
(0, prostgles_types_1.isEmpty)(this.filter) ||
!Object.keys(this.filter).find((k) => this.filter[k] !== item[k])));
}
matchesIdObj(a, b) {
return Boolean(a && b && !this.id_fields.sort().find((k) => a[k] !== b[k]));
}
// TODO: offline-first deletes if allow_delete = true
// setDeleted(idObj, fullArray){
// let deleted: object[] = [];
// if(fullArray) deleted = fullArray;
// else {
// deleted = this.getDeleted();
// deleted.push(idObj);
// }
// if(hasWnd) window.localStorage.setItem(this.name + "_$$psql$$_deleted", <any>deleted);
// }
// getDeleted(){
// const delStr = if(hasWnd) window.localStorage.getItem(this.name + "_$$psql$$_deleted") || '[]';
// return JSON.parse(delStr);
// }
// syncDeleted = async () => {
// try {
// await Promise.all(this.getDeleted().map(async idObj => {
// return this.db[this.name].delete(idObj);
// }));
// this.setDeleted(null, []);
// return true;
// } catch(e){
// throw e;
// }
// }
/**
* Returns properties that are present in {n} and are different to {o}
* @param o current full data item
* @param n new data item
*/
getDelta(o, n) {
if ((0, prostgles_types_1.isEmpty)(o))
return { ...n };
return Object.fromEntries(Object.entries({ ...n })
.filter(([k]) => !this.id_fields.includes(k))
.map(([k, v]) => {
if (!(0, prostgles_types_1.isEqual)(v, o[k])) {
const vClone = (0, prostgles_types_1.isObject)(v) ? { ...v }
: Array.isArray(v) ? v.slice(0)
: v;
return [k, vClone];
}
})
.filter(prostgles_types_1.isDefined));
}
deleteAll() {
this.getItems().map((d) => this.delete(d));
}
get tableHandler() {
const tblHandler = this.db[this.name];
if ((tblHandler === null || tblHandler === void 0 ? void 0 : tblHandler.update) && tblHandler.updateBatch) {
return tblHandler;
}
return undefined;
}
/* Returns an item by idObj from the local store */
getItem(idObj) {
let d;
if (this.storageType === STORAGE_TYPES.localStorage) {
const items = this.getItems();
d = items.find((d) => this.matchesIdObj(d, idObj));
}
else {
d = this.itemsMap.get(this.getIdStr(idObj));
}
return (0, exports.quickClone)(d);
}
/**
*
* @param item data to be inserted/updated/deleted. Must include id_fields
* @param index (optional) index within array
* @param isFullData
* @param deleteItem
*/
setItem(_item, isFullData = false, deleteItem = false) {
var _a;
const item = (0, exports.quickClone)(_item);
if (this.storageType === STORAGE_TYPES.localStorage) {
let items = this.getItems();
if (deleteItem) {
items = items.filter((d) => !this.matchesIdObj(d, item));
}
else {
let exists = false;
items = items.map((d) => {
if (this.matchesIdObj(d, item)) {
exists = true;
return isFullData ? { ...item } : { ...d, ...item };
}
return d;
});
if (!exists) {
items.push(item);
}
}
if (hasWnd) {
window.localStorage.setItem(this.name, JSON.stringify(items));
}
}
else {
const id = this.getIdStr(item);
if (deleteItem) {
this.itemsMap.delete(id);
}
else {
const existing = (_a = this.itemsMap.get(id)) !== null && _a !== void 0 ? _a : {};
this.itemsMap.set(id, isFullData ? { ...item } : { ...existing, ...item });
}
}
}
}
exports.SyncedTable = SyncedTable;
const mergeDeep = (_target, _source) => {
const target = _target ? (0, exports.quickClone)(_target) : _target;
const source = _source ? (0, exports.quickClone)(_source) : _source;
const output = (0, prostgles_types_1.isObject)(target) ? { ...target } : {};
if ((0, prostgles_types_1.isObject)(target) && (0, prostgles_types_1.isObject)(source)) {
Object.keys(source).forEach((sourceKey) => {
const sourceValue = source[sourceKey];
const targetValue = target[sourceKey];
if ((0, prostgles_types_1.isObject)(sourceValue) && (0, prostgles_types_1.isObject)(targetValue)) {
output[sourceKey] = (0, exports.mergeDeep)(targetValue, sourceValue);
}
else {
output[sourceKey] = (0, exports.quickClone)(sourceValue);
}
});
}
return output;
};
exports.mergeDeep = mergeDeep;
const quickClone = (obj) => {
if (hasWnd && "structuredClone" in window && typeof window.structuredClone === "function") {
return window.structuredClone(obj);
}
if (Array.isArray(obj)) {
return obj.slice(0).map((v) => (0, exports.quickClone)(v));
}
else if ((0, prostgles_types_1.isObject)(obj)) {
const result = {};
(0, prostgles_types_1.getKeys)(obj).map((k) => {
result[k] = (0, exports.quickClone)(obj[k]);
});
return result;
}
return obj;
};
exports.quickClone = quickClone;
/**
* Type tests
*/
const typeTest = async () => {
const s = 1;
const sh = s({ a: 1 }, {}, (d) => { });
const syncTyped = 1;
// const sUntyped: Sync<AnyObject, any> = syncTyped;
};