@scalar/object-utils
Advanced tools
Comparing version 1.1.2 to 1.1.3
# @scalar/object-utils | ||
## 1.1.3 | ||
### Patch Changes | ||
- 04ca40b: Move to vanilla rollup | ||
## 1.1.2 | ||
@@ -4,0 +10,0 @@ |
@@ -1,6 +0,1 @@ | ||
import { alphaSort as t, sortByOrder as e, timeSort as a } from "./sort.js"; | ||
export { | ||
t as alphaSort, | ||
e as sortByOrder, | ||
a as timeSort | ||
}; | ||
export { alphaSort, sortByOrder, timeSort } from './sort.js'; |
@@ -1,22 +0,35 @@ | ||
function i(n, o, t) { | ||
const r = (t ? n[t] : n) ?? "", c = (t ? o[t] : o) ?? ""; | ||
return new Date(r).getTime() - new Date(c).getTime(); | ||
/** Date sorting for arrays */ | ||
function timeSort(a, b, key) { | ||
const valA = ((key ? a[key] : a) ?? ''); | ||
const valB = ((key ? b[key] : b) ?? ''); | ||
return new Date(valA).getTime() - new Date(valB).getTime(); | ||
} | ||
function l(n, o, t) { | ||
const r = String((t ? n[t] : n) ?? ""), c = String((t ? o[t] : o) ?? ""); | ||
return r.localeCompare(c); | ||
/** Sort alphanumerically */ | ||
function alphaSort(a, b, key) { | ||
const valA = String((key ? a[key] : a) ?? ''); | ||
const valB = String((key ? b[key] : b) ?? ''); | ||
return valA.localeCompare(valB); | ||
} | ||
function u(n, o, t) { | ||
const r = {}; | ||
o.forEach((e, a) => r[e] = a); | ||
const c = [], s = []; | ||
return n.forEach((e) => { | ||
const a = r[e[t]] ?? -1; | ||
a >= 0 ? c[a] = e : s.push(e); | ||
}), c.concat(...s); | ||
/** | ||
* Immutably sorts a list by another list with O(n) time | ||
* Returns a sorted copy with any unsorted items at the end of list | ||
*/ | ||
function sortByOrder(arr, order, idKey) { | ||
// Map the order to keep a single lookup table | ||
const orderMap = {}; | ||
order.forEach((e, idx) => (orderMap[e] = idx)); | ||
const sorted = []; | ||
const untagged = []; | ||
arr.forEach((e) => { | ||
const sortedIdx = orderMap[e[idKey]] ?? -1; | ||
if (sortedIdx >= 0) { | ||
sorted[sortedIdx] = e; | ||
} | ||
else { | ||
untagged.push(e); | ||
} | ||
}); | ||
return sorted.concat(...untagged); | ||
} | ||
export { | ||
l as alphaSort, | ||
u as sortByOrder, | ||
i as timeSort | ||
}; | ||
export { alphaSort, sortByOrder, timeSort }; |
@@ -1,4 +0,1 @@ | ||
import { default as a } from "just-clone"; | ||
export { | ||
a as clone | ||
}; | ||
export { default as clone } from 'just-clone'; |
@@ -1,46 +0,50 @@ | ||
import { Mutation as u } from "./mutations.js"; | ||
const l = 500; | ||
function w(e, c, t = l) { | ||
function r(n) { | ||
const o = c[n]; | ||
return o || console.warn( | ||
`Missing ${e[n] ? "mutator" : "object"} for uid: ${n}` | ||
), o ?? null; | ||
} | ||
return { | ||
/** Adds a new item to the record of tracked items and creates a new mutation tracking instance */ | ||
add: (n) => { | ||
e[n.uid] = n, c[n.uid] = new u(n, t); | ||
}, | ||
delete: (n) => { | ||
delete e[n], delete c[n]; | ||
}, | ||
/** Destructive, overwrites a record to a new item and creates a new mutation tracking instance */ | ||
set: (n) => { | ||
e[n.uid] = n, c[n.uid] = new u(n, t); | ||
}, | ||
/** Update a nested property and track the mutation */ | ||
edit: (n, o, s) => { | ||
const d = r(n); | ||
d == null || d.mutate(o, s); | ||
}, | ||
/** Commit an untracked edit to the object (undo/redo will not work) */ | ||
untrackedEdit: (n, o, s) => { | ||
const d = r(n); | ||
d == null || d._unsavedMutate(o, s); | ||
}, | ||
/** Undo the last mutation */ | ||
undo: (n) => { | ||
const o = r(n); | ||
o == null || o.undo(); | ||
}, | ||
/** Redo a mutation if available */ | ||
redo: (n) => { | ||
const o = r(n); | ||
o == null || o.redo(); | ||
import { Mutation } from './mutations.js'; | ||
const MAX_MUTATION_RECORDS = 500; | ||
/** Generate mutation handlers for a given record of objects */ | ||
function mutationFactory(entityMap, mutationMap, maxNumberRecords = MAX_MUTATION_RECORDS) { | ||
function getMutator(uid) { | ||
const mutator = mutationMap[uid]; | ||
if (!mutator) | ||
console.warn(`Missing ${entityMap[uid] ? 'mutator' : 'object'} for uid: ${uid}`); | ||
return mutator ?? null; | ||
} | ||
}; | ||
return { | ||
/** Adds a new item to the record of tracked items and creates a new mutation tracking instance */ | ||
add: (item) => { | ||
entityMap[item.uid] = item; | ||
mutationMap[item.uid] = new Mutation(item, maxNumberRecords); | ||
}, | ||
delete: (uid) => { | ||
delete entityMap[uid]; | ||
delete mutationMap[uid]; | ||
}, | ||
/** Destructive, overwrites a record to a new item and creates a new mutation tracking instance */ | ||
set: (item) => { | ||
entityMap[item.uid] = item; | ||
mutationMap[item.uid] = new Mutation(item, maxNumberRecords); | ||
}, | ||
/** Update a nested property and track the mutation */ | ||
edit: (uid, path, value) => { | ||
const mutator = getMutator(uid); | ||
mutator?.mutate(path, value); | ||
}, | ||
/** Commit an untracked edit to the object (undo/redo will not work) */ | ||
untrackedEdit: (uid, path, value) => { | ||
const mutator = getMutator(uid); | ||
mutator?._unsavedMutate(path, value); | ||
}, | ||
/** Undo the last mutation */ | ||
undo: (uid) => { | ||
const mutator = getMutator(uid); | ||
mutator?.undo(); | ||
}, | ||
/** Redo a mutation if available */ | ||
redo: (uid) => { | ||
const mutator = getMutator(uid); | ||
mutator?.redo(); | ||
}, | ||
}; | ||
} | ||
export { | ||
w as mutationFactory | ||
}; | ||
export { mutationFactory }; |
@@ -1,7 +0,2 @@ | ||
import { Mutation as r, includes as a } from "./mutations.js"; | ||
import { mutationFactory as i } from "./handlers.js"; | ||
export { | ||
r as Mutation, | ||
a as includes, | ||
i as mutationFactory | ||
}; | ||
export { Mutation, includes } from './mutations.js'; | ||
export { mutationFactory } from './handlers.js'; |
@@ -1,67 +0,114 @@ | ||
var u = Object.defineProperty; | ||
var h = (i, t, e) => t in i ? u(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e; | ||
var r = (i, t, e) => h(i, typeof t != "symbol" ? t + "" : t, e); | ||
import { setNestedValue as n, getNestedValue as c } from "../nested/nested.js"; | ||
function l(i, t) { | ||
return i.includes(t); | ||
import { setNestedValue, getNestedValue } from '../nested/nested.js'; | ||
/** Type safe include */ | ||
function includes(arr, x) { | ||
return arr.includes(x); | ||
} | ||
class x { | ||
constructor(t, e = 5e3, s = !1) { | ||
/** | ||
* Mutation tracker to allow history roll back/forwards | ||
* | ||
* Associates a history record with a specific data object and allows rolling back of that | ||
* specific object history. | ||
*/ | ||
class Mutation { | ||
/** Object reference for the given data to be tracked */ | ||
r(this, "parentData"); | ||
parentData; | ||
/** Maximum number of record to keep (how many times you can 'undo' a mutation) */ | ||
r(this, "maxRecords"); | ||
maxRecords; | ||
/** List of all mutation records */ | ||
r(this, "records", []); | ||
records = []; | ||
/** List of side effect handlers to run whenever the data changes */ | ||
r(this, "sideEffects", []); | ||
sideEffects = []; | ||
/** Active mutation index. Allows rolling forward and backwards */ | ||
r(this, "idx", 0); | ||
idx = 0; | ||
/** Optional debug messages */ | ||
r(this, "debug"); | ||
this.maxRecords = e, this.parentData = t, this.debug = s; | ||
} | ||
/** Mutate without saving a record. Private function. */ | ||
_unsavedMutate(t, e) { | ||
n(this.parentData, t, e), this.runSideEffects(t); | ||
} | ||
/** Side effects must take ONLY an object of the specified type and act on it */ | ||
addSideEffect(t, e, s, d = !0) { | ||
this.sideEffects.push({ triggers: t, effect: e, name: s }), d && (e(this.parentData), this.debug && console.info(`Running mutation side effect: ${s}`, "debug")); | ||
} | ||
/** Runs all side effects that match the path trigger */ | ||
runSideEffects(t) { | ||
this.sideEffects.forEach(({ effect: e, triggers: s, name: d }) => { | ||
(s.some((o) => t.includes(o)) || t.length < 1) && (e(this.parentData), this.debug && console.info(`Running mutation side effect: ${d}`, "debug")); | ||
}); | ||
} | ||
/** Mutate an object with the new property value and run side effects */ | ||
mutate(t, e, s = null) { | ||
this.idx < this.records.length - 1 && this.records.splice(this.idx + 1); | ||
const d = c(this.parentData, t); | ||
d !== e && (n(this.parentData, t, e), this.runSideEffects(t), this.records.push({ | ||
prev: s ?? d, | ||
// Optional explicit previous value | ||
value: e, | ||
path: t | ||
}), this.idx = this.records.length - 1, this.records.length > this.maxRecords && this.records.shift(), this.debug && console.info(`Set object '${this.idx}' '${t}' to ${e}`, "debug")); | ||
} | ||
/** Undo the previous mutation */ | ||
undo() { | ||
if (this.idx < 0 || this.records.length < 1) return !1; | ||
this.debug && console.info("Undoing Mutation", "debug"); | ||
const t = this.records[this.idx]; | ||
return this.idx -= 1, this._unsavedMutate(t.path, t.prev), !0; | ||
} | ||
/** Roll forward to the next available mutation if its exists */ | ||
redo() { | ||
if (this.idx > this.records.length - 2) return !1; | ||
this.debug && console.info("Redoing Mutation", "debug"); | ||
const t = this.records[this.idx + 1]; | ||
return this.idx += 1, this._unsavedMutate(t.path, t.value), !0; | ||
} | ||
debug; | ||
constructor(parentData, maxRecords = 5000, debug = false) { | ||
this.maxRecords = maxRecords; | ||
this.parentData = parentData; | ||
this.debug = debug; | ||
} | ||
/** Mutate without saving a record. Private function. */ | ||
_unsavedMutate(path, value) { | ||
setNestedValue(this.parentData, path, value); | ||
this.runSideEffects(path); | ||
} | ||
/** Side effects must take ONLY an object of the specified type and act on it */ | ||
addSideEffect(triggers, effect, name, immediate = true) { | ||
this.sideEffects.push({ triggers, effect, name }); | ||
if (immediate) { | ||
effect(this.parentData); | ||
if (this.debug) { | ||
console.info(`Running mutation side effect: ${name}`, 'debug'); | ||
} | ||
} | ||
} | ||
/** Runs all side effects that match the path trigger */ | ||
runSideEffects(path) { | ||
this.sideEffects.forEach(({ effect, triggers, name }) => { | ||
const triggerEffect = triggers.some((trigger) => path.includes(trigger)) || path.length < 1; | ||
if (triggerEffect) { | ||
effect(this.parentData); | ||
if (this.debug) { | ||
console.info(`Running mutation side effect: ${name}`, 'debug'); | ||
} | ||
} | ||
}); | ||
} | ||
/** Mutate an object with the new property value and run side effects */ | ||
mutate( | ||
/** Path to nested set */ | ||
path, | ||
/** New value to set */ | ||
value, | ||
/** Optional explicit previous value. Otherwise the current value will be used */ | ||
previousValue = null) { | ||
// If already rolled back then clear roll forward values before assigning new mutation | ||
if (this.idx < this.records.length - 1) | ||
this.records.splice(this.idx + 1); | ||
// Check for a change | ||
const prev = getNestedValue(this.parentData, path); | ||
if (prev === value) | ||
return; | ||
// Save new mutation record with previous value | ||
setNestedValue(this.parentData, path, value); | ||
this.runSideEffects(path); | ||
this.records.push({ | ||
prev: previousValue ?? prev, // Optional explicit previous value | ||
value, | ||
path, | ||
}); | ||
// Save new position to end | ||
this.idx = this.records.length - 1; | ||
// If the record has overflowed remove first entry | ||
if (this.records.length > this.maxRecords) | ||
this.records.shift(); | ||
if (this.debug) { | ||
console.info(`Set object '${this.idx}' '${path}' to ${value}`, 'debug'); | ||
} | ||
} | ||
/** Undo the previous mutation */ | ||
undo() { | ||
if (this.idx < 0 || this.records.length < 1) | ||
return false; | ||
if (this.debug) | ||
console.info('Undoing Mutation', 'debug'); | ||
const record = this.records[this.idx]; | ||
this.idx -= 1; | ||
this._unsavedMutate(record.path, record.prev); | ||
return true; | ||
} | ||
/** Roll forward to the next available mutation if its exists */ | ||
redo() { | ||
if (this.idx > this.records.length - 2) | ||
return false; | ||
if (this.debug) | ||
console.info('Redoing Mutation', 'debug'); | ||
const record = this.records[this.idx + 1]; | ||
this.idx += 1; | ||
this._unsavedMutate(record.path, record.value); | ||
return true; | ||
} | ||
} | ||
export { | ||
x as Mutation, | ||
l as includes | ||
}; | ||
export { Mutation, includes }; |
@@ -1,5 +0,1 @@ | ||
import { getNestedValue as s, setNestedValue as a } from "./nested.js"; | ||
export { | ||
s as getNestedValue, | ||
a as setNestedValue | ||
}; | ||
export { getNestedValue, setNestedValue } from './nested.js'; |
@@ -1,11 +0,37 @@ | ||
function l(e, s, r) { | ||
const t = s.split("."), n = t.at(-1); | ||
return t.reduce((i, u) => (u === n && (i[u] = r), i[u]), e), e; | ||
// --------------------------------------------------------------------------- | ||
// --------------------------------------------------------------------------- | ||
// --------------------------------------------------------------------------- | ||
/** | ||
* Set a nested value from an object using a dot separated path. | ||
* | ||
* Basic Path: `'foo.bar'` | ||
* | ||
* With Array: `'foo.1.bar'` | ||
*/ | ||
function setNestedValue(obj, path, value) { | ||
const keys = path.split('.'); | ||
const lastKey = keys.at(-1); | ||
// Loop over to get the nested object reference. Then assign the value to it | ||
keys.reduce((acc, current) => { | ||
if (current === lastKey) | ||
acc[current] = value; | ||
return acc[current]; | ||
}, obj); | ||
return obj; | ||
} | ||
function o(e, s) { | ||
return s.split(".").reduce((t, n) => t[n], e); | ||
/** | ||
* Get a nested value from an object using a dot separated path. | ||
* | ||
* Basic Path: `'foo.bar'` | ||
* | ||
* With Array: `'foo.1.bar'` | ||
*/ | ||
function getNestedValue(obj, path) { | ||
const keys = path.split('.'); | ||
// Loop over to get the nested object reference. Then assign the value to it | ||
return keys.reduce((acc, current) => { | ||
return acc[current]; | ||
}, obj); | ||
} | ||
export { | ||
o as getNestedValue, | ||
l as setNestedValue | ||
}; | ||
export { getNestedValue, setNestedValue }; |
@@ -1,4 +0,1 @@ | ||
import { objectFromArray as e } from "./object-from-array.js"; | ||
export { | ||
e as objectFromArray | ||
}; | ||
export { objectFromArray } from './object-from-array.js'; |
@@ -1,6 +0,24 @@ | ||
function r(e, n) { | ||
return e.reduce((i, o) => (i[n(o)] && console.warn(`Duplicate entry in object mapping for ${o}`), i[n(o)] = o, i), {}); | ||
/** | ||
* Return a hash map from an array object using a generated key | ||
* | ||
* Example: | ||
* ``` | ||
* const mappedObj = | ||
* objectFromArray([{ key: '1', name: 'one' }, { key: '2', name: 'two'}], (item) => item.key) | ||
* | ||
* const result = { | ||
* '1': { key: '1', name: 'one' }, | ||
* '2': { key: '2', name: 'two' } | ||
* } | ||
* ``` | ||
*/ | ||
function objectFromArray(data, keyGenerator) { | ||
return data.reduce((map, current) => { | ||
if (map[keyGenerator(current)]) | ||
console.warn(`Duplicate entry in object mapping for ${current}`); | ||
map[keyGenerator(current)] = current; | ||
return map; | ||
}, {}); | ||
} | ||
export { | ||
r as objectFromArray | ||
}; | ||
export { objectFromArray }; |
@@ -16,3 +16,3 @@ { | ||
], | ||
"version": "1.1.2", | ||
"version": "1.1.3", | ||
"engines": { | ||
@@ -58,8 +58,7 @@ "node": ">=18" | ||
"devDependencies": { | ||
"tsc-alias": "^1.8.8", | ||
"vite": "^5.2.10", | ||
"@scalar/build-tooling": "0.1.7" | ||
"@scalar/build-tooling": "0.1.8" | ||
}, | ||
"scripts": { | ||
"build": "vite build && pnpm types:build && tsc-alias -p tsconfig.build.json", | ||
"build": "scalar-build-rollup", | ||
"dev": "vite", | ||
@@ -70,5 +69,5 @@ "lint:check": "eslint .", | ||
"test": "vitest", | ||
"types:build": "tsc -p tsconfig.build.json", | ||
"types:check": "tsc --noEmit --skipLibCheck --composite false" | ||
"types:build": "scalar-types-build", | ||
"types:check": "scalar-types-check" | ||
} | ||
} |
49195
2
834