Skip to content

Commit dde34ed

Browse files
committed
fix(loki): fix error if passed parameters are not serializable for transform (#43)
Using new 'CloneMethod.SHALLOW_RECURSE_OBJECTS' for transform parameter substitution to avoid error when base transform step(s) have non serializable properties. (from techfort/LokiJS#624)
1 parent 3509429 commit dde34ed

File tree

3 files changed

+96
-8
lines changed

3 files changed

+96
-8
lines changed

packages/loki/spec/generic/transforms.spec.ts

+77
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,83 @@ describe("transforms", () => {
8888
});
8989
});
9090

91+
describe("parameterized transform with non-serializable non-params", function () {
92+
it("works", function () {
93+
94+
interface Person {
95+
name: string;
96+
age: number;
97+
}
98+
99+
100+
const db = new Loki("tx.db");
101+
const items = db.addCollection<Person>("items");
102+
103+
items.insert({name: "mjolnir", age: 5});
104+
items.insert({name: "tyrfing", age: 9});
105+
106+
let mapper = function (item: Person) {
107+
return item.age;
108+
};
109+
110+
let averageReduceFunction = function (values: number[]) {
111+
let sum = 0;
112+
113+
values.forEach(function (i) {
114+
sum += i;
115+
});
116+
117+
return sum / values.length;
118+
};
119+
120+
// so ideally, transform params are useful for
121+
// - extracting values that will change across multiple executions, and also
122+
// - extracting values which are not serializable so that the transform can be
123+
// named and serialized along with the database.
124+
//
125+
// The transform used here is not serializable so this test is just to verify
126+
// that our parameter substitution method does not have problem with
127+
// non-serializable transforms.
128+
129+
let tx1 = [
130+
{
131+
type: "mapReduce",
132+
mapFunction: mapper,
133+
reduceFunction: averageReduceFunction
134+
}
135+
];
136+
137+
let tx2 = [
138+
{
139+
type: "find",
140+
value: {
141+
age: {
142+
"$gt": "[%lktxp]minimumAge"
143+
},
144+
}
145+
},
146+
{
147+
type: "mapReduce",
148+
mapFunction: mapper,
149+
reduceFunction: averageReduceFunction
150+
}
151+
] as any;
152+
153+
// no data() call needed to mapReduce
154+
expect(items.chain(tx1) as any as number).toBe(7);
155+
expect(items.chain(tx1, {foo: 5}) as any as number).toBe(7);
156+
// params will cause a recursive shallow clone of objects before substitution
157+
expect(items.chain(tx2, {minimumAge: 4}) as any as number).toBe(7);
158+
159+
// make sure original transform is unchanged
160+
expect(tx2[0].type).toEqual("find");
161+
expect(tx2[0].value.age.$gt).toEqual("[%lktxp]minimumAge");
162+
expect(tx2[1].type).toEqual("mapReduce");
163+
expect(typeof tx2[1].mapFunction).toEqual("function");
164+
expect(typeof tx2[1].reduceFunction).toEqual("function");
165+
});
166+
});
167+
91168
describe("parameterized where", () => {
92169
it("works", () => {
93170

packages/loki/src/clone.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
/* global jQuery */
21
export type ANY = any;
32

4-
export function clone<T>(data: T, method: CloneMethod = CloneMethod.PARSE_STRINGIFY) : T {
3+
export function clone<T>(data: T, method: CloneMethod = CloneMethod.PARSE_STRINGIFY): T {
54
if (data === null || data === undefined) {
65
return null;
76
}
87

9-
let cloned: object;
8+
let cloned: any;
109

1110
switch (method) {
1211
case CloneMethod.PARSE_STRINGIFY:
@@ -28,6 +27,18 @@ export function clone<T>(data: T, method: CloneMethod = CloneMethod.PARSE_STRING
2827
cloned = Object.create(data.constructor.prototype);
2928
Object.assign(cloned, data);
3029
break;
30+
case CloneMethod.SHALLOW_RECURSE_OBJECTS:
31+
// shallow clone top level properties
32+
cloned = clone(data, CloneMethod.SHALLOW);
33+
const keys = Object.keys(data);
34+
// for each of the top level properties which are object literals, recursively shallow copy
35+
for (let i = 0; i < keys.length; i++) {
36+
const key = keys[i];
37+
if (typeof data[key] === "object" && data[key].constructor.name === "Object") {
38+
cloned[key] = clone(data[key], CloneMethod.SHALLOW_RECURSE_OBJECTS);
39+
}
40+
}
41+
break;
3142
default:
3243
break;
3344
}
@@ -57,4 +68,5 @@ export enum CloneMethod {
5768
JQUERY_EXTEND_DEEP,
5869
SHALLOW,
5970
SHALLOW_ASSIGN,
71+
SHALLOW_RECURSE_OBJECTS,
6072
}

packages/loki/src/utils.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
/**
2-
* Created by toni on 1/27/17.
3-
*/
1+
import {clone, CloneMethod} from "./clone";
2+
43
export type ANY = any;
54

65
export function copyProperties(src: object, dest: object) {
@@ -41,8 +40,8 @@ export function resolveTransformParams(transform: ANY, params: object) {
4140

4241
// iterate all steps in the transform array
4342
for (idx = 0; idx < transform.length; idx++) {
44-
// clone transform so our scan and replace can operate directly on cloned transform
45-
clonedStep = JSON.parse(JSON.stringify(transform[idx]));
43+
// clone transform so our scan/replace can operate directly on cloned transform
44+
clonedStep = clone(transform[idx], CloneMethod.SHALLOW_RECURSE_OBJECTS);
4645
resolvedTransform.push(resolveTransformObject(clonedStep, params));
4746
}
4847

0 commit comments

Comments
 (0)