@derivesome/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.README.md.~undo-tree~ +4 -0
- package/.package.json.~undo-tree~ +30 -0
- package/.tsconfig.cjs.json.~undo-tree~ +8 -0
- package/.tsconfig.json.~undo-tree~ +14 -0
- package/README.md +355 -0
- package/README.md~ +355 -0
- package/dist/cjs/array-proxy.d.ts +56 -0
- package/dist/cjs/array-proxy.d.ts.map +1 -0
- package/dist/cjs/array-proxy.js +72 -0
- package/dist/cjs/array-proxy.js.map +1 -0
- package/dist/cjs/common/array.d.ts +11 -0
- package/dist/cjs/common/array.d.ts.map +1 -0
- package/dist/cjs/common/array.js +57 -0
- package/dist/cjs/common/array.js.map +1 -0
- package/dist/cjs/common/compare.d.ts +2 -0
- package/dist/cjs/common/compare.d.ts.map +1 -0
- package/dist/cjs/common/compare.js +8 -0
- package/dist/cjs/common/compare.js.map +1 -0
- package/dist/cjs/common/diff.d.ts +9 -0
- package/dist/cjs/common/diff.d.ts.map +1 -0
- package/dist/cjs/common/diff.js +63 -0
- package/dist/cjs/common/diff.js.map +1 -0
- package/dist/cjs/common/has.d.ts +4 -0
- package/dist/cjs/common/has.d.ts.map +1 -0
- package/dist/cjs/common/has.js +11 -0
- package/dist/cjs/common/has.js.map +1 -0
- package/dist/cjs/common/index.d.ts +12 -0
- package/dist/cjs/common/index.d.ts.map +1 -0
- package/dist/cjs/common/index.js +28 -0
- package/dist/cjs/common/index.js.map +1 -0
- package/dist/cjs/common/is.d.ts +5 -0
- package/dist/cjs/common/is.d.ts.map +1 -0
- package/dist/cjs/common/is.js +18 -0
- package/dist/cjs/common/is.js.map +1 -0
- package/dist/cjs/common/match.d.ts +23 -0
- package/dist/cjs/common/match.d.ts.map +1 -0
- package/dist/cjs/common/match.js +49 -0
- package/dist/cjs/common/match.js.map +1 -0
- package/dist/cjs/common/patch.d.ts +3 -0
- package/dist/cjs/common/patch.d.ts.map +1 -0
- package/dist/cjs/common/patch.js +44 -0
- package/dist/cjs/common/patch.js.map +1 -0
- package/dist/cjs/common/stack.d.ts +8 -0
- package/dist/cjs/common/stack.d.ts.map +1 -0
- package/dist/cjs/common/stack.js +24 -0
- package/dist/cjs/common/stack.js.map +1 -0
- package/dist/cjs/common/string.d.ts +2 -0
- package/dist/cjs/common/string.d.ts.map +1 -0
- package/dist/cjs/common/string.js +36 -0
- package/dist/cjs/common/string.js.map +1 -0
- package/dist/cjs/common/types.d.ts +27 -0
- package/dist/cjs/common/types.d.ts.map +1 -0
- package/dist/cjs/common/types.js +3 -0
- package/dist/cjs/common/types.js.map +1 -0
- package/dist/cjs/common/unique-array.d.ts +17 -0
- package/dist/cjs/common/unique-array.d.ts.map +1 -0
- package/dist/cjs/common/unique-array.js +73 -0
- package/dist/cjs/common/unique-array.js.map +1 -0
- package/dist/cjs/context.d.ts +15 -0
- package/dist/cjs/context.d.ts.map +1 -0
- package/dist/cjs/context.js +33 -0
- package/dist/cjs/context.js.map +1 -0
- package/dist/cjs/derived.d.ts +11 -0
- package/dist/cjs/derived.d.ts.map +1 -0
- package/dist/cjs/derived.js +26 -0
- package/dist/cjs/derived.js.map +1 -0
- package/dist/cjs/effect.d.ts +7 -0
- package/dist/cjs/effect.d.ts.map +1 -0
- package/dist/cjs/effect.js +7 -0
- package/dist/cjs/effect.js.map +1 -0
- package/dist/cjs/index.d.ts +8 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +24 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/observable.d.ts +12 -0
- package/dist/cjs/observable.d.ts.map +1 -0
- package/dist/cjs/observable.js +24 -0
- package/dist/cjs/observable.js.map +1 -0
- package/dist/cjs/pubsub.d.ts +11 -0
- package/dist/cjs/pubsub.d.ts.map +1 -0
- package/dist/cjs/pubsub.js +50 -0
- package/dist/cjs/pubsub.js.map +1 -0
- package/dist/cjs/reference.d.ts +15 -0
- package/dist/cjs/reference.d.ts.map +1 -0
- package/dist/cjs/reference.js +49 -0
- package/dist/cjs/reference.js.map +1 -0
- package/dist/cjs/utils/findRefs.d.ts +3 -0
- package/dist/cjs/utils/findRefs.d.ts.map +1 -0
- package/dist/cjs/utils/findRefs.js +23 -0
- package/dist/cjs/utils/findRefs.js.map +1 -0
- package/dist/cjs/utils/index.d.ts +2 -0
- package/dist/cjs/utils/index.d.ts.map +1 -0
- package/dist/cjs/utils/index.js +18 -0
- package/dist/cjs/utils/index.js.map +1 -0
- package/dist/esm/array-proxy.d.ts +56 -0
- package/dist/esm/array-proxy.d.ts.map +1 -0
- package/dist/esm/array-proxy.js +68 -0
- package/dist/esm/array-proxy.js.map +1 -0
- package/dist/esm/common/array.d.ts +11 -0
- package/dist/esm/common/array.d.ts.map +1 -0
- package/dist/esm/common/array.js +48 -0
- package/dist/esm/common/array.js.map +1 -0
- package/dist/esm/common/compare.d.ts +2 -0
- package/dist/esm/common/compare.d.ts.map +1 -0
- package/dist/esm/common/compare.js +4 -0
- package/dist/esm/common/compare.js.map +1 -0
- package/dist/esm/common/diff.d.ts +9 -0
- package/dist/esm/common/diff.d.ts.map +1 -0
- package/dist/esm/common/diff.js +59 -0
- package/dist/esm/common/diff.js.map +1 -0
- package/dist/esm/common/has.d.ts +4 -0
- package/dist/esm/common/has.d.ts.map +1 -0
- package/dist/esm/common/has.js +8 -0
- package/dist/esm/common/has.js.map +1 -0
- package/dist/esm/common/index.d.ts +12 -0
- package/dist/esm/common/index.d.ts.map +1 -0
- package/dist/esm/common/index.js +12 -0
- package/dist/esm/common/index.js.map +1 -0
- package/dist/esm/common/is.d.ts +5 -0
- package/dist/esm/common/is.d.ts.map +1 -0
- package/dist/esm/common/is.js +13 -0
- package/dist/esm/common/is.js.map +1 -0
- package/dist/esm/common/match.d.ts +23 -0
- package/dist/esm/common/match.d.ts.map +1 -0
- package/dist/esm/common/match.js +46 -0
- package/dist/esm/common/match.js.map +1 -0
- package/dist/esm/common/patch.d.ts +3 -0
- package/dist/esm/common/patch.d.ts.map +1 -0
- package/dist/esm/common/patch.js +40 -0
- package/dist/esm/common/patch.js.map +1 -0
- package/dist/esm/common/stack.d.ts +8 -0
- package/dist/esm/common/stack.d.ts.map +1 -0
- package/dist/esm/common/stack.js +20 -0
- package/dist/esm/common/stack.js.map +1 -0
- package/dist/esm/common/string.d.ts +2 -0
- package/dist/esm/common/string.d.ts.map +1 -0
- package/dist/esm/common/string.js +32 -0
- package/dist/esm/common/string.js.map +1 -0
- package/dist/esm/common/types.d.ts +27 -0
- package/dist/esm/common/types.d.ts.map +1 -0
- package/dist/esm/common/types.js +2 -0
- package/dist/esm/common/types.js.map +1 -0
- package/dist/esm/common/unique-array.d.ts +17 -0
- package/dist/esm/common/unique-array.d.ts.map +1 -0
- package/dist/esm/common/unique-array.js +69 -0
- package/dist/esm/common/unique-array.js.map +1 -0
- package/dist/esm/context.d.ts +15 -0
- package/dist/esm/context.d.ts.map +1 -0
- package/dist/esm/context.js +28 -0
- package/dist/esm/context.js.map +1 -0
- package/dist/esm/derived.d.ts +11 -0
- package/dist/esm/derived.d.ts.map +1 -0
- package/dist/esm/derived.js +21 -0
- package/dist/esm/derived.js.map +1 -0
- package/dist/esm/effect.d.ts +7 -0
- package/dist/esm/effect.d.ts.map +1 -0
- package/dist/esm/effect.js +3 -0
- package/dist/esm/effect.js.map +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/observable.d.ts +12 -0
- package/dist/esm/observable.d.ts.map +1 -0
- package/dist/esm/observable.js +19 -0
- package/dist/esm/observable.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/pubsub.d.ts +11 -0
- package/dist/esm/pubsub.d.ts.map +1 -0
- package/dist/esm/pubsub.js +47 -0
- package/dist/esm/pubsub.js.map +1 -0
- package/dist/esm/reference.d.ts +15 -0
- package/dist/esm/reference.d.ts.map +1 -0
- package/dist/esm/reference.js +44 -0
- package/dist/esm/reference.js.map +1 -0
- package/dist/esm/utils/findRefs.d.ts +3 -0
- package/dist/esm/utils/findRefs.d.ts.map +1 -0
- package/dist/esm/utils/findRefs.js +19 -0
- package/dist/esm/utils/findRefs.js.map +1 -0
- package/dist/esm/utils/index.d.ts +2 -0
- package/dist/esm/utils/index.d.ts.map +1 -0
- package/dist/esm/utils/index.js +2 -0
- package/dist/esm/utils/index.js.map +1 -0
- package/package.json +41 -0
- package/package.json~ +42 -0
- package/src/array-proxy.ts +94 -0
- package/src/common/array.ts +72 -0
- package/src/common/compare.ts +3 -0
- package/src/common/diff.test.ts +114 -0
- package/src/common/diff.ts +85 -0
- package/src/common/has.ts +7 -0
- package/src/common/index.ts +11 -0
- package/src/common/is.ts +14 -0
- package/src/common/match.ts +90 -0
- package/src/common/patch.test.ts +118 -0
- package/src/common/patch.ts +47 -0
- package/src/common/stack.ts +22 -0
- package/src/common/string.test.ts +46 -0
- package/src/common/string.ts +35 -0
- package/src/common/types.ts +61 -0
- package/src/common/unique-array.ts +80 -0
- package/src/context.ts +36 -0
- package/src/derived.test.ts +45 -0
- package/src/derived.ts +35 -0
- package/src/effect.ts +9 -0
- package/src/index.ts +7 -0
- package/src/observable.ts +40 -0
- package/src/pubsub.test.ts +29 -0
- package/src/pubsub.ts +71 -0
- package/src/reference.test.ts +32 -0
- package/src/reference.ts +63 -0
- package/src/utils/findRefs.ts +22 -0
- package/src/utils/index.ts +1 -0
- package/tsconfig.cjs.json +8 -0
- package/tsconfig.cjs.json~ +0 -0
- package/tsconfig.json +24 -0
- package/tsconfig.json~ +24 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { has } from "./common";
|
|
2
|
+
export const OBSERVABLE = Symbol("Observable");
|
|
3
|
+
export function observable(state, ps) {
|
|
4
|
+
const observe = (fn, options = {}) => {
|
|
5
|
+
if (options.immediate) {
|
|
6
|
+
fn(state.value);
|
|
7
|
+
}
|
|
8
|
+
return ps.subscribe(fn, options.cleanup);
|
|
9
|
+
};
|
|
10
|
+
return {
|
|
11
|
+
[OBSERVABLE]: true,
|
|
12
|
+
...ps,
|
|
13
|
+
observe,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function isObservable(x) {
|
|
17
|
+
return has(x, OBSERVABLE);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=observable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observable.js","sourceRoot":"","sources":["../../src/observable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAS/B,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAQ/C,MAAM,UAAU,UAAU,CAAI,KAAe,EAAE,EAAa;IAC1D,MAAM,OAAO,GAAG,CACd,EAAmB,EACnB,UAA4B,EAAE,EAChB,EAAE;QAChB,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACtB,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC,CAAC;IAEF,OAAO;QACL,CAAC,UAAU,CAAC,EAAE,IAAI;QAClB,GAAG,EAAE;QACL,OAAO;KACR,CAAC;AACJ,CAAC;AAID,MAAM,UAAU,YAAY,CAAC,CAAU;IACrC,OAAO,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Subscription, VoidFunction } from "./common/types";
|
|
2
|
+
export interface PubSub<T> {
|
|
3
|
+
subscribers: Set<Subscription<T>>;
|
|
4
|
+
effects: Set<VoidFunction>;
|
|
5
|
+
publish: (value: T) => void;
|
|
6
|
+
subscribe: (fn: Subscription<T>, cleanup?: VoidFunction) => VoidFunction;
|
|
7
|
+
addEffect: (effect: VoidFunction, cleanup?: VoidFunction) => VoidFunction;
|
|
8
|
+
dispose: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function pubsub<T>(): PubSub<T>;
|
|
11
|
+
//# sourceMappingURL=pubsub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pubsub.d.ts","sourceRoot":"","sources":["../../src/pubsub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG5D,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,WAAW,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,OAAO,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;IAC3B,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,SAAS,EAAE,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,YAAY,KAAK,YAAY,CAAC;IACzE,SAAS,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,YAAY,KAAK,YAAY,CAAC;IAC1E,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,CA0DrC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Context } from "./context";
|
|
2
|
+
export function pubsub() {
|
|
3
|
+
const subscribers = new Set();
|
|
4
|
+
const effects = new Set();
|
|
5
|
+
const trash = new Set();
|
|
6
|
+
const addTrash = (fn) => {
|
|
7
|
+
trash.add(fn);
|
|
8
|
+
return fn;
|
|
9
|
+
};
|
|
10
|
+
const publish = (value) => {
|
|
11
|
+
effects.forEach((fx) => {
|
|
12
|
+
Context.runEffect(fx);
|
|
13
|
+
});
|
|
14
|
+
subscribers.forEach((sub) => {
|
|
15
|
+
sub(value);
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
const addEffect = (effect, cleanup) => {
|
|
19
|
+
effects.add(effect);
|
|
20
|
+
return addTrash(() => {
|
|
21
|
+
effects.delete(effect);
|
|
22
|
+
cleanup?.();
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const subscribe = (fn, cleanup) => {
|
|
26
|
+
subscribers.add(fn);
|
|
27
|
+
return addTrash(() => {
|
|
28
|
+
subscribers.delete(fn);
|
|
29
|
+
cleanup?.();
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const dispose = () => {
|
|
33
|
+
trash.forEach((fn) => fn());
|
|
34
|
+
trash.clear();
|
|
35
|
+
subscribers.clear();
|
|
36
|
+
effects.clear();
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
subscribers,
|
|
40
|
+
effects,
|
|
41
|
+
publish,
|
|
42
|
+
subscribe,
|
|
43
|
+
addEffect,
|
|
44
|
+
dispose,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=pubsub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pubsub.js","sourceRoot":"","sources":["../../src/pubsub.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,MAAM,UAAU,MAAM;IACpB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC/C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAgB,CAAC;IACxC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgB,CAAC;IAEtC,MAAM,QAAQ,GAAG,CAAC,EAAgB,EAAgB,EAAE;QAClD,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACd,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,KAAQ,EAAQ,EAAE;QACjC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;YACrB,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACxB,CAAC,CAAC,CAAC;QACH,WAAW,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YAC1B,GAAG,CAAC,KAAK,CAAC,CAAC;QACb,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAChB,MAAoB,EACpB,OAAsB,EACR,EAAE;QAChB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAEpB,OAAO,QAAQ,CAAC,GAAG,EAAE;YACnB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACvB,OAAO,EAAE,EAAE,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAChB,EAAmB,EACnB,OAAsB,EACR,EAAE;QAChB,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEpB,OAAO,QAAQ,CAAC,GAAG,EAAE;YACnB,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACvB,OAAO,EAAE,EAAE,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5B,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,WAAW,CAAC,KAAK,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO;QACL,WAAW;QACX,OAAO;QACP,OAAO;QACP,SAAS;QACT,SAAS;QACT,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Dispatch, Loose, SetStateAction } from "./common/types";
|
|
2
|
+
import { Observable } from "./observable";
|
|
3
|
+
export declare const REFERENCE: unique symbol;
|
|
4
|
+
export type REFERENCE = typeof REFERENCE;
|
|
5
|
+
export interface Reference<T> extends Observable<T> {
|
|
6
|
+
[REFERENCE]: true;
|
|
7
|
+
get: () => T;
|
|
8
|
+
set: Dispatch<SetStateAction<T>>;
|
|
9
|
+
peek: () => T;
|
|
10
|
+
}
|
|
11
|
+
export declare function ref<T>(value: T): Reference<T>;
|
|
12
|
+
export declare function isReference(x: Loose): x is Reference<Loose>;
|
|
13
|
+
export declare function isReference(x: unknown): x is Reference<unknown>;
|
|
14
|
+
export declare function isReference<X extends Reference<unknown>>(x: X): x is X;
|
|
15
|
+
//# sourceMappingURL=reference.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference.d.ts","sourceRoot":"","sources":["../../src/reference.ts"],"names":[],"mappings":"AAGA,OAAO,EAAS,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGxE,OAAO,EAAc,UAAU,EAAE,MAAM,cAAc,CAAC;AAGtD,eAAO,MAAM,SAAS,eAAsB,CAAC;AAC7C,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC;AAEzC,MAAM,WAAW,SAAS,CAAC,CAAC,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAC;IACjD,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC,CAAC;CACf;AAED,wBAAgB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAoC7C;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC;AAC7D,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC;AACjE,wBAAgB,WAAW,CAAC,CAAC,SAAS,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { has } from "./common";
|
|
2
|
+
import { compare } from "./common/compare";
|
|
3
|
+
import { isFunction } from "./common/is";
|
|
4
|
+
import { Context } from "./context";
|
|
5
|
+
import { observable } from "./observable";
|
|
6
|
+
import { pubsub } from "./pubsub";
|
|
7
|
+
export const REFERENCE = Symbol("Reference");
|
|
8
|
+
export function ref(value) {
|
|
9
|
+
const ps = pubsub();
|
|
10
|
+
const state = { value };
|
|
11
|
+
const obs = observable(state, ps);
|
|
12
|
+
const addEffectDependency = (fx) => {
|
|
13
|
+
ps.addEffect(fx.run, fx.cleanup);
|
|
14
|
+
};
|
|
15
|
+
const get = () => {
|
|
16
|
+
const fx = Context.scope.current.effect;
|
|
17
|
+
if (fx)
|
|
18
|
+
addEffectDependency(fx);
|
|
19
|
+
return state.value;
|
|
20
|
+
};
|
|
21
|
+
const set = (fn) => {
|
|
22
|
+
const prev = state.value;
|
|
23
|
+
const next = isFunction(fn) ? fn(state.value) : fn;
|
|
24
|
+
if (!compare(next, prev)) {
|
|
25
|
+
state.value = next;
|
|
26
|
+
ps.publish(state.value);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const peek = () => {
|
|
30
|
+
return state.value;
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
[REFERENCE]: true,
|
|
34
|
+
...ps,
|
|
35
|
+
...obs,
|
|
36
|
+
get,
|
|
37
|
+
set,
|
|
38
|
+
peek,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function isReference(x) {
|
|
42
|
+
return has(x, REFERENCE);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=reference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference.js","sourceRoot":"","sources":["../../src/reference.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,UAAU,EAAc,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;AAU7C,MAAM,UAAU,GAAG,CAAI,KAAQ;IAC7B,MAAM,EAAE,GAAG,MAAM,EAAK,CAAC;IACvB,MAAM,KAAK,GAAa,EAAE,KAAK,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,UAAU,CAAI,KAAK,EAAE,EAAE,CAAC,CAAC;IAErC,MAAM,mBAAmB,GAAG,CAAC,EAAU,EAAE,EAAE;QACzC,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC,CAAC;IAEF,MAAM,GAAG,GAAG,GAAM,EAAE;QAClB,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QACxC,IAAI,EAAE;YAAE,mBAAmB,CAAC,EAAE,CAAC,CAAC;QAChC,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC,CAAC;IAEF,MAAM,GAAG,GAAgC,CAAC,EAAE,EAAE,EAAE;QAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC;QACzB,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACnD,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACzB,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC;YACnB,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,GAAM,EAAE;QACnB,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC,CAAC;IAEF,OAAO;QACL,CAAC,SAAS,CAAC,EAAE,IAAI;QACjB,GAAG,EAAE;QACL,GAAG,GAAG;QACN,GAAG;QACH,GAAG;QACH,IAAI;KACL,CAAC;AACJ,CAAC;AAKD,MAAM,UAAU,WAAW,CAAC,CAAU;IACpC,OAAO,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findRefs.d.ts","sourceRoot":"","sources":["../../../src/utils/findRefs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,SAAS,EAAE,MAAM,cAAc,CAAC;AAEtD,eAAO,MAAM,QAAQ,GAAI,GAAG,OAAO,KAAG,SAAS,CAAC,OAAO,CAAC,EAmBvD,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { isReference } from "../reference";
|
|
2
|
+
export const findRefs = (x) => {
|
|
3
|
+
if (typeof x === 'undefined' || x === null)
|
|
4
|
+
return [];
|
|
5
|
+
if (isReference(x))
|
|
6
|
+
return [x];
|
|
7
|
+
if (Array.isArray(x)) {
|
|
8
|
+
return x.map(it => findRefs(it)).flat();
|
|
9
|
+
}
|
|
10
|
+
if (typeof x === 'object' && x !== null) {
|
|
11
|
+
const refs = [];
|
|
12
|
+
for (const [_k, v] of Object.entries(x)) {
|
|
13
|
+
refs.push(...findRefs(v));
|
|
14
|
+
}
|
|
15
|
+
return refs;
|
|
16
|
+
}
|
|
17
|
+
return [];
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=findRefs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findRefs.js","sourceRoot":"","sources":["../../../src/utils/findRefs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAa,MAAM,cAAc,CAAC;AAEtD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAU,EAAwB,EAAE;IAE3D,IAAI,OAAO,CAAC,KAAK,WAAW,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IACtD,IAAI,WAAW,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAG/B,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QACxC,MAAM,IAAI,GAA8B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YACxC,IAAI,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@derivesome/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/cjs/index.js",
|
|
6
|
+
"types": "./dist/cjs/index.d.ts",
|
|
7
|
+
"typings": "./dist/cjs/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "vitest",
|
|
10
|
+
"build": "tsc -p tsconfig.json && echo '{\"type\":\"module\"}' > dist/esm/package.json && tsc -p tsconfig.cjs.json",
|
|
11
|
+
"format": "prettier ./src --write",
|
|
12
|
+
"check": "tsc -p ./tsconfig.json --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [],
|
|
15
|
+
"author": "",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"packageManager": "pnpm@10.28.2",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/esm/index.d.ts",
|
|
22
|
+
"default": "./dist/esm/index.js"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/cjs/index.d.ts",
|
|
26
|
+
"default": "./dist/cjs/index.js"
|
|
27
|
+
},
|
|
28
|
+
"default": {
|
|
29
|
+
"types": "./dist/cjs/index.d.ts",
|
|
30
|
+
"default": "./dist/cjs/index.js"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^25.2.1",
|
|
36
|
+
"prettier": "^3.8.1",
|
|
37
|
+
"tsx": "^4.21.0",
|
|
38
|
+
"typescript": "^5.9.3",
|
|
39
|
+
"vitest": "^4.0.18"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/package.json~
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@derivesome/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "",
|
|
6
|
+
"main": "./dist/cjs/index.js",
|
|
7
|
+
"types": "./dist/cjs/index.d.ts",
|
|
8
|
+
"typings": "./dist/cjs/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "vitest",
|
|
11
|
+
"build": "tsc -p tsconfig.json && echo '{\"type\":\"module\"}' > dist/esm/package.json && tsc -p tsconfig.cjs.json",
|
|
12
|
+
"format": "prettier ./src --write",
|
|
13
|
+
"check": "tsc -p ./tsconfig.json --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [],
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"packageManager": "pnpm@10.28.2",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/esm/index.d.ts",
|
|
23
|
+
"default": "./dist/esm/index.js"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/cjs/index.d.ts",
|
|
27
|
+
"default": "./dist/cjs/index.js"
|
|
28
|
+
},
|
|
29
|
+
"default": {
|
|
30
|
+
"types": "./dist/cjs/index.d.ts",
|
|
31
|
+
"default": "./dist/cjs/index.js"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^25.2.1",
|
|
37
|
+
"prettier": "^3.8.1",
|
|
38
|
+
"tsx": "^4.21.0",
|
|
39
|
+
"typescript": "^5.9.3",
|
|
40
|
+
"vitest": "^4.0.18"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { PubSub, pubsub } from "./pubsub";
|
|
2
|
+
import { VoidFunction } from "./common/types";
|
|
3
|
+
|
|
4
|
+
export type ArrayMutation<T> =
|
|
5
|
+
| { type: "push"; items: T[] }
|
|
6
|
+
| { type: "pop"; removed: T | undefined }
|
|
7
|
+
| { type: "shift"; removed: T | undefined }
|
|
8
|
+
| { type: "unshift"; items: T[] }
|
|
9
|
+
| { type: "splice"; start: number; deleteCount: number; removed: T[]; added: T[] }
|
|
10
|
+
| { type: "sort" }
|
|
11
|
+
| { type: "reverse" }
|
|
12
|
+
| { type: "fill"; value: T; start: number; end: number }
|
|
13
|
+
| { type: "copyWithin"; target: number; start: number; end: number }
|
|
14
|
+
| { type: "set"; index: number; value: T };
|
|
15
|
+
|
|
16
|
+
export class ArrayProxy<T> extends Array<T> {
|
|
17
|
+
readonly ps: PubSub<ArrayMutation<T>>;
|
|
18
|
+
|
|
19
|
+
constructor(...items: T[]) {
|
|
20
|
+
super(...items);
|
|
21
|
+
this.ps = pubsub<ArrayMutation<T>>();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
subscribe(fn: (mutation: ArrayMutation<T>) => void, cleanup?: VoidFunction): VoidFunction {
|
|
25
|
+
return this.ps.subscribe(fn, cleanup);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override push(...items: T[]): number {
|
|
29
|
+
const result = super.push(...items);
|
|
30
|
+
this.ps.publish({ type: "push", items });
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override pop(): T | undefined {
|
|
35
|
+
const removed = super.pop();
|
|
36
|
+
this.ps.publish({ type: "pop", removed });
|
|
37
|
+
return removed;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override shift(): T | undefined {
|
|
41
|
+
const removed = super.shift();
|
|
42
|
+
this.ps.publish({ type: "shift", removed });
|
|
43
|
+
return removed;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
override unshift(...items: T[]): number {
|
|
47
|
+
const result = super.unshift(...items);
|
|
48
|
+
this.ps.publish({ type: "unshift", items });
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override splice(start: number, deleteCount?: number, ...items: T[]): T[] {
|
|
53
|
+
const dc = deleteCount ?? this.length - start;
|
|
54
|
+
const removed = super.splice(start, dc, ...items);
|
|
55
|
+
this.ps.publish({ type: "splice", start, deleteCount: dc, removed, added: items });
|
|
56
|
+
return removed;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
override sort(compareFn?: (a: T, b: T) => number): this {
|
|
60
|
+
super.sort(compareFn);
|
|
61
|
+
this.ps.publish({ type: "sort" });
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override reverse(): this {
|
|
66
|
+
super.reverse();
|
|
67
|
+
this.ps.publish({ type: "reverse" });
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override fill(value: T, start?: number, end?: number): this {
|
|
72
|
+
const s = start ?? 0;
|
|
73
|
+
const e = end ?? this.length;
|
|
74
|
+
super.fill(value, s, e);
|
|
75
|
+
this.ps.publish({ type: "fill", value, start: s, end: e });
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override copyWithin(target: number, start: number, end?: number): this {
|
|
80
|
+
const e = end ?? this.length;
|
|
81
|
+
super.copyWithin(target, start, e);
|
|
82
|
+
this.ps.publish({ type: "copyWithin", target, start, end: e });
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
set(index: number, value: T): void {
|
|
87
|
+
this[index] = value;
|
|
88
|
+
this.ps.publish({ type: "set", index, value });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
dispose(): void {
|
|
92
|
+
this.ps.dispose();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export const range = (n: number): Array<number> =>
|
|
2
|
+
n <= 0 ? [] : Array.from(Array(Math.floor(n)).keys());
|
|
3
|
+
|
|
4
|
+
export const enumerate = <T>(arr: T[]): Array<[number, T]> => {
|
|
5
|
+
return arr.map((v, i) => [i, v]);
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const zipMin = <A, B>(a: A[], b: B[]): Array<[A, B]> => {
|
|
9
|
+
const length = Math.min(a.length, b.length);
|
|
10
|
+
return range(length).map((i) => [a[i]!, b[i]!]);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const zipMax = <A, B>(a: A[], b: B[]): Array<[A | undefined, B | undefined]> => {
|
|
14
|
+
const length = Math.max(a.length, b.length);
|
|
15
|
+
return range(length).map((i) => [a[i], b[i]]);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const zipByKey = <A, B>(
|
|
19
|
+
a: A[],
|
|
20
|
+
b: B[],
|
|
21
|
+
getKey: <T extends A | B>(item: T) => PropertyKey | null,
|
|
22
|
+
): { pairs: Array<[A, B]>, restA: A[], restB: B[] } => {
|
|
23
|
+
const keys = Array.from(
|
|
24
|
+
new Set(
|
|
25
|
+
[...a.map((x) => getKey(x)), ...b.map((x) => getKey(x))].filter(
|
|
26
|
+
(x) => x !== null,
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const ma = new Map(
|
|
32
|
+
a
|
|
33
|
+
.map((x): [PropertyKey | null, A] => [getKey(x), x])
|
|
34
|
+
.filter(([k, _]) => k !== null),
|
|
35
|
+
);
|
|
36
|
+
const mb = new Map(
|
|
37
|
+
b
|
|
38
|
+
.map((x): [PropertyKey | null, B] => [getKey(x), x])
|
|
39
|
+
.filter(([k, _]) => k !== null),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const z: Array<[A, B]> = [];
|
|
43
|
+
const restA: A[] = [];
|
|
44
|
+
const restB: B[] = [];
|
|
45
|
+
const seenA = new Set<A>();
|
|
46
|
+
const seenB = new Set<B>();
|
|
47
|
+
|
|
48
|
+
for (const k of keys) {
|
|
49
|
+
const va = ma.get(k) ?? null;
|
|
50
|
+
const vb = mb.get(k) ?? null;
|
|
51
|
+
|
|
52
|
+
if (va !== null && vb === null) {
|
|
53
|
+
restA.push(va);
|
|
54
|
+
seenA.add(va);
|
|
55
|
+
} else if (va === null && vb !== null) {
|
|
56
|
+
restB.push(vb);
|
|
57
|
+
seenB.add(vb);
|
|
58
|
+
} else if (va !== null && vb !== null) {
|
|
59
|
+
z.push([va, vb]);
|
|
60
|
+
seenA.add(va);
|
|
61
|
+
seenB.add(vb);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
return { pairs: z, restA, restB };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
export const unique = <T>(arr: T[]): T[] => {
|
|
71
|
+
return Array.from(new Set<T>(arr));
|
|
72
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, it, assert } from 'vitest';
|
|
2
|
+
import { diff } from './diff';
|
|
3
|
+
|
|
4
|
+
describe('diff', () => {
|
|
5
|
+
it('should return empty array for identical primitives', () => {
|
|
6
|
+
assert.deepEqual(diff(1, 1), []);
|
|
7
|
+
assert.deepEqual(diff('hello', 'hello'), []);
|
|
8
|
+
assert.deepEqual(diff(true, true), []);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should detect changed primitives', () => {
|
|
12
|
+
const result = diff(1, 2);
|
|
13
|
+
assert.deepEqual(result, [
|
|
14
|
+
{ type: 'changed', path: [], oldValue: 1, newValue: 2 },
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return empty array for identical objects', () => {
|
|
19
|
+
assert.deepEqual(diff({ a: 1, b: 2 }, { a: 1, b: 2 }), []);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should detect changed properties', () => {
|
|
23
|
+
const result = diff({ a: 1 }, { a: 2 });
|
|
24
|
+
assert.deepEqual(result, [
|
|
25
|
+
{ type: 'changed', path: ['a'], oldValue: 1, newValue: 2 },
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should detect added properties', () => {
|
|
30
|
+
const result = diff({ a: 1 } as any, { a: 1, b: 2 } as any);
|
|
31
|
+
assert.deepEqual(result, [
|
|
32
|
+
{ type: 'added', path: ['b'], newValue: 2 },
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should detect removed properties', () => {
|
|
37
|
+
const result = diff({ a: 1, b: 2 } as any, { a: 1 } as any);
|
|
38
|
+
assert.deepEqual(result, [
|
|
39
|
+
{ type: 'removed', path: ['b'], oldValue: 2 },
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should handle nested object diffs', () => {
|
|
44
|
+
const a = { x: { y: { z: 1 } } };
|
|
45
|
+
const b = { x: { y: { z: 2 } } };
|
|
46
|
+
const result = diff(a, b);
|
|
47
|
+
assert.deepEqual(result, [
|
|
48
|
+
{ type: 'changed', path: ['x', 'y', 'z'], oldValue: 1, newValue: 2 },
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should handle array element changes', () => {
|
|
53
|
+
const result = diff([1, 2, 3], [1, 9, 3]);
|
|
54
|
+
assert.deepEqual(result, [
|
|
55
|
+
{ type: 'changed', path: [1], oldValue: 2, newValue: 9 },
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should detect added array elements', () => {
|
|
60
|
+
const result = diff([1, 2], [1, 2, 3]);
|
|
61
|
+
assert.deepEqual(result, [
|
|
62
|
+
{ type: 'added', path: [2], newValue: 3 },
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should detect removed array elements', () => {
|
|
67
|
+
const result = diff([1, 2, 3], [1, 2]);
|
|
68
|
+
assert.deepEqual(result, [
|
|
69
|
+
{ type: 'removed', path: [2], oldValue: 3 },
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should handle nested arrays inside objects', () => {
|
|
74
|
+
const a = { items: [1, 2] };
|
|
75
|
+
const b = { items: [1, 3] };
|
|
76
|
+
const result = diff(a, b);
|
|
77
|
+
assert.deepEqual(result, [
|
|
78
|
+
{ type: 'changed', path: ['items', 1], oldValue: 2, newValue: 3 },
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should detect type change from object to primitive', () => {
|
|
83
|
+
const result = diff({ a: { b: 1 } } as any, { a: 42 } as any);
|
|
84
|
+
assert.deepEqual(result, [
|
|
85
|
+
{ type: 'changed', path: ['a'], oldValue: { b: 1 }, newValue: 42 },
|
|
86
|
+
]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should detect type change from array to object', () => {
|
|
90
|
+
const result = diff({ a: [1, 2] } as any, { a: { x: 1 } } as any);
|
|
91
|
+
assert.deepEqual(result, [
|
|
92
|
+
{ type: 'changed', path: ['a'], oldValue: [1, 2], newValue: { x: 1 } },
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should handle symbol keys', () => {
|
|
97
|
+
const sym = Symbol('test');
|
|
98
|
+
const a = { [sym]: 1 };
|
|
99
|
+
const b = { [sym]: 2 };
|
|
100
|
+
const result = diff(a, b);
|
|
101
|
+
assert.deepEqual(result, [
|
|
102
|
+
{ type: 'changed', path: [sym], oldValue: 1, newValue: 2 },
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should handle deeply nested mixed structures', () => {
|
|
107
|
+
const a = { users: [{ name: 'Alice', scores: [10, 20] }] };
|
|
108
|
+
const b = { users: [{ name: 'Alice', scores: [10, 30] }] };
|
|
109
|
+
const result = diff(a, b);
|
|
110
|
+
assert.deepEqual(result, [
|
|
111
|
+
{ type: 'changed', path: ['users', 0, 'scores', 1], oldValue: 20, newValue: 30 },
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { isPlainObject } from './is';
|
|
2
|
+
|
|
3
|
+
export type DiffType = 'added' | 'removed' | 'changed';
|
|
4
|
+
|
|
5
|
+
export type Diff = {
|
|
6
|
+
type: DiffType;
|
|
7
|
+
path: PropertyKey[];
|
|
8
|
+
oldValue?: unknown;
|
|
9
|
+
newValue?: unknown;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const diff = <T>(a: T, b: T): Diff[] => {
|
|
13
|
+
const diffs: Diff[] = [];
|
|
14
|
+
diffRecursive(a, b, [], diffs);
|
|
15
|
+
return diffs;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const diffRecursive = (a: unknown, b: unknown, path: PropertyKey[], diffs: Diff[]): void => {
|
|
19
|
+
if (a === b) return;
|
|
20
|
+
|
|
21
|
+
const aIsArray = Array.isArray(a);
|
|
22
|
+
const bIsArray = Array.isArray(b);
|
|
23
|
+
|
|
24
|
+
if (aIsArray && bIsArray) {
|
|
25
|
+
diffArrays(a, b, path, diffs);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const aIsObject = isPlainObject(a);
|
|
30
|
+
const bIsObject = isPlainObject(b);
|
|
31
|
+
|
|
32
|
+
if (aIsObject && bIsObject) {
|
|
33
|
+
diffObjects(a, b, path, diffs);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
diffs.push({ type: 'changed', path, oldValue: a, newValue: b });
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const diffObjects = (
|
|
41
|
+
a: Record<PropertyKey, unknown>,
|
|
42
|
+
b: Record<PropertyKey, unknown>,
|
|
43
|
+
path: PropertyKey[],
|
|
44
|
+
diffs: Diff[],
|
|
45
|
+
): void => {
|
|
46
|
+
const aKeys = Reflect.ownKeys(a);
|
|
47
|
+
const bKeys = Reflect.ownKeys(b);
|
|
48
|
+
const bKeySet = new Set(bKeys);
|
|
49
|
+
|
|
50
|
+
for (const key of aKeys) {
|
|
51
|
+
const childPath = [...path, key];
|
|
52
|
+
if (!bKeySet.has(key)) {
|
|
53
|
+
diffs.push({ type: 'removed', path: childPath, oldValue: a[key] });
|
|
54
|
+
} else {
|
|
55
|
+
diffRecursive(a[key], b[key], childPath, diffs);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const aKeySet = new Set(aKeys);
|
|
60
|
+
for (const key of bKeys) {
|
|
61
|
+
if (!aKeySet.has(key)) {
|
|
62
|
+
diffs.push({ type: 'added', path: [...path, key], newValue: b[key] });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const diffArrays = (
|
|
68
|
+
a: unknown[],
|
|
69
|
+
b: unknown[],
|
|
70
|
+
path: PropertyKey[],
|
|
71
|
+
diffs: Diff[],
|
|
72
|
+
): void => {
|
|
73
|
+
const maxLen = Math.max(a.length, b.length);
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < maxLen; i++) {
|
|
76
|
+
const childPath = [...path, i];
|
|
77
|
+
if (i >= a.length) {
|
|
78
|
+
diffs.push({ type: 'added', path: childPath, newValue: b[i] });
|
|
79
|
+
} else if (i >= b.length) {
|
|
80
|
+
diffs.push({ type: 'removed', path: childPath, oldValue: a[i] });
|
|
81
|
+
} else {
|
|
82
|
+
diffRecursive(a[i], b[i], childPath, diffs);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|