Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ CDN usage:
| Web performance & UI | `debounce`, `throttle`, `LRUCache`, `memoize`, `deduplicateRequest`, `clearRequestDedup`, `calculateVirtualRange`, `createWeightedAliasSampler`, `createObjectPool`, `fisherYatesShuffle` | `util/*.ts` | `examples/requestDedup.ts`, `examples/virtualScroll.ts`, `examples/weightedAlias.ts`, `examples/objectPool.ts`, `examples/fisherYates.ts` |
| Gameplay systems | `createDeltaTimeManager`, `createFixedTimestepLoop`, `createCamera2D`, `createParticleSystem`, `createSpriteAnimation`, `createTweenSystem`, `createPlatformerController`, `createTopDownController`, `createTileMapController`, `computeFieldOfView`, `createInventory`, `calculateDamage`, `createCooldownController`, `updateStatusEffects`, `createQuestMachine`, `createWaveSpawner`, `createSoundManager`, `createInputManager`, `createSaveManager`, `createScreenTransition` | `util/deltaTime.ts`, `util/fixedTimestep.ts`, `gameplay/camera2D.ts`, `gameplay/particleSystem.ts`, `gameplay/spriteAnimation.ts`, `gameplay/tween.ts`, `gameplay/platformerPhysics.ts`, `gameplay/topDownMovement.ts`, `gameplay/tileMap.ts`, `gameplay/shadowcasting.ts`, `gameplay/inventory.ts`, `gameplay/combat.ts`, `gameplay/questMachine.ts`, `gameplay/waveSpawner.ts`, `gameplay/soundManager.ts`, `gameplay/inputManager.ts`, `gameplay/saveManager.ts`, `gameplay/screenTransitions.ts` | `examples/deltaTime.ts`, `examples/fixedTimestep.ts`, `examples/camera2D.ts`, `examples/particleSystem.ts`, `examples/spriteAnimation.ts`, `examples/tween.ts`, `examples/platformerPhysics.ts`, `examples/topDownMovement.ts`, `examples/tileMap.ts`, `examples/shadowcasting.ts`, `examples/inventory.ts`, `examples/combat.ts`, `examples/quest.ts`, `examples/waveSpawner.ts`, `examples/soundManager.ts`, `examples/inputManager.ts`, `examples/saveManager.ts`, `examples/screenTransitions.ts` |
| Search & text | `fuzzySearch`, `fuzzyScore`, `Trie`, `binarySearch`, `levenshteinDistance`, `kmpSearch`, `rabinKarp`, `boyerMooreSearch`, `buildSuffixArray`, `longestCommonSubsequence`, `diffStrings` | `search/*.ts` | `examples/search.ts` |
| Data & diff pipelines | `diff`, `deepClone`, `groupBy`, `diffJson`, `applyJsonDiff`, `flatten`, `unflatten` | `data/*.ts` | `examples/jsonDiff.ts` |
| Data & diff pipelines | `diff`, `deepClone`, `groupBy`, `diffJson`, `diffJsonAdvanced`, `applyJsonDiff`, `applyJsonDiffSelective`, `flatten`, `unflatten`, `diffTree`, `applyTreeDiff` | `data/*.ts` | `examples/jsonDiff.ts`, `examples/treeDiff.ts` |
| Graph algorithms | `graphBFS`, `graphDFS`, `topologicalSort` | `graph/traversal.ts` | `examples/graph.ts` |
| Visual & geometry | `convexHull`, `lineIntersection`, `pointInPolygon`, `bresenhamLine`, `easing`, `quadraticBezier`, `cubicBezier` | `geometry/*.ts`, `visual/*.ts` | `examples/geometry.ts`, `examples/bresenham.ts`, `examples/visual.ts` |

Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
- **Data pipelines & utilities**
- [x] Flatten/unflatten helpers for nested structures
- [x] Pagination utilities for client-side paging
- [ ] Advanced diff tooling (tree diff, selective patches)
- [x] Advanced diff tooling (tree diff, selective patches)
- **Visual & simulation tools**
- [ ] Color manipulation helpers (RGB/HSL conversion, blending)
- [ ] Force-directed graph layout
Expand Down
61 changes: 61 additions & 0 deletions docs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2697,6 +2697,15 @@ export function diffJsonAdvanced(
next: JsonValue,
options?: DiffJsonAdvancedOptions
): JsonDiffOperation[];
export interface ApplyJsonDiffOptions {
shouldApply?: (operation: JsonDiffOperation) => boolean;
pathFilter?: (path: JsonPathSegment[]) => boolean;
}
export function applyJsonDiffSelective<T extends JsonValue>(
value: T,
diff: JsonDiffOperation[],
options?: ApplyJsonDiffOptions
): JsonValue;

/**
* Flattens nested structures into key/value pairs.
Expand Down Expand Up @@ -2743,6 +2752,58 @@ export interface PaginationResult<T> {
}
export function paginate<T>(options: PaginateOptions<T>): PaginationResult<T>;

/**
* Diffs tree structures while preserving node identity.
* Use for: UI virtual DOM reconciliation, scene graphs, hierarchical state.
* Import: data/treeDiff.ts
*/
export interface TreeNode<TValue = unknown> {
id: string;
value?: TValue;
children?: TreeNode<TValue>[];
}
export interface TreeInsertOperation<TValue = unknown> {
type: 'insert';
id: string;
parentId: string | null;
index: number;
node: TreeNode<TValue>;
}
export interface TreeRemoveOperation {
type: 'remove';
id: string;
parentId: string | null;
}
export interface TreeMoveOperation {
type: 'move';
id: string;
parentId: string | null;
index: number;
}
export interface TreeUpdateOperation<TValue = unknown> {
type: 'update';
id: string;
value: TValue | undefined;
hasValue: boolean;
}
export type TreeDiffOperation<TValue = unknown> =
| TreeInsertOperation<TValue>
| TreeRemoveOperation
| TreeMoveOperation
| TreeUpdateOperation<TValue>;
export interface TreeDiffOptions<TValue = unknown> {
isEqual?: (previous: TreeNode<TValue>, next: TreeNode<TValue>) => boolean;
}
export function diffTree<TValue = unknown>(
previous: ReadonlyArray<TreeNode<TValue>>,
next: ReadonlyArray<TreeNode<TValue>>,
options?: TreeDiffOptions<TValue>
): TreeDiffOperation<TValue>[];
export function applyTreeDiff<TValue = unknown>(
tree: ReadonlyArray<TreeNode<TValue>>,
diff: ReadonlyArray<TreeDiffOperation<TValue>>
): TreeNode<TValue>[];

/**
* Deep clone structured data.
* Use for: immutability, snapshots, undo buffers.
Expand Down
1 change: 1 addition & 0 deletions docs/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ Unflatten - Convert flat to nested structure
Group By - Organize data by property
Deep Clone - Recursive object copying
Pagination - Client-side data paging
Tree Diff - Compare hierarchical structures

🎨 VISUAL & ANIMATION

Expand Down
14 changes: 13 additions & 1 deletion examples/jsonDiff.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { applyJsonDiff, diffJson, diffJsonAdvanced, flatten, unflatten } from '../src/index.js';
import {
applyJsonDiff,
applyJsonDiffSelective,
diffJson,
diffJsonAdvanced,
flatten,
unflatten,
} from '../src/index.js';

const previous = { status: 'idle', jobs: ['ingest', 'transform'] };
const next = { status: 'running', jobs: ['ingest', 'transform', 'export'] };
Expand All @@ -18,3 +25,8 @@ const selectivePatch = diffJsonAdvanced(previous, next, {
ignoreKeys: ['jobs'],
});
console.log('Selective patch (ignore jobs):', selectivePatch);

const selectivelyApplied = applyJsonDiffSelective(previous, patch, {
pathFilter: (path) => path.join('.') !== 'jobs',
});
console.log('Selective apply (ignore jobs):', selectivelyApplied);
34 changes: 34 additions & 0 deletions examples/treeDiff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { applyTreeDiff, diffTree } from '../src/index.js';

const previous = [
{
id: 'root',
value: { label: 'Root' },
children: [
{ id: 'a', value: { label: 'A' }, children: [{ id: 'a-1', value: { label: 'A-1' } }] },
{ id: 'b', value: { label: 'B' } },
],
},
];

const next = [
{
id: 'root',
value: { label: 'Root (updated)' },
children: [
{ id: 'b', value: { label: 'B' } },
{
id: 'wrapper',
value: { label: 'Wrapper' },
children: [{ id: 'a', value: { label: 'A', active: true }, children: [] }],
},
{ id: 'c', value: { label: 'C' } },
],
},
];

const diff = diffTree(previous, next);
console.log('Tree diff operations:', diff);

const patched = applyTreeDiff(previous, diff);
console.log('Patched tree:', JSON.stringify(patched, null, 2));
32 changes: 32 additions & 0 deletions src/data/jsonDiff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,26 @@ export function diffJsonAdvanced(
* Applies a JSON diff to a value and returns a new structure.
* Useful for: reconstructing snapshots, applying remote patches, optimistic updates.
*/
export interface ApplyJsonDiffOptions {
shouldApply?: (operation: JsonDiffOperation) => boolean;
pathFilter?: (path: JsonPathSegment[]) => boolean;
}

export function applyJsonDiff<T extends JsonValue>(value: T, diff: JsonDiffOperation[]): JsonValue {
return applyJsonDiffSelective(value, diff);
}

export function applyJsonDiffSelective<T extends JsonValue>(
value: T,
diff: JsonDiffOperation[],
options: ApplyJsonDiffOptions = {}
): JsonValue {
const predicate = createDiffPredicate(options);
let result: JsonValue = deepClone(value);
for (const operation of diff) {
if (!predicate(operation)) {
continue;
}
result = applyOperation(result, operation);
}
return result;
Expand Down Expand Up @@ -197,6 +214,21 @@ function applyObjectOperation(
return root;
}

function createDiffPredicate(options: ApplyJsonDiffOptions): (operation: JsonDiffOperation) => boolean {
if (!options.shouldApply && !options.pathFilter) {
return () => true;
}
return (operation) => {
if (options.pathFilter && !options.pathFilter(operation.path)) {
return false;
}
if (options.shouldApply && !options.shouldApply(operation)) {
return false;
}
return true;
};
}

function resolveParent(value: JsonValue, path: JsonPathSegment[], allowCreate: boolean): JsonValue | null {
if (path.length === 0) {
return value;
Expand Down
Loading