@util.js/privates 0.41.5 → 0.41.7
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/package.json +6 -2
- package/lib/Privates.js +0 -192
- package/test/PrivatesTest.js +0 -202
package/package.json
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
"author": "Chris Topher <c@creemama.com>",
|
|
3
3
|
"dependencies": {
|
|
4
4
|
"@babel/runtime-corejs3": "7.29.7",
|
|
5
|
-
"@util.js/errors": "0.41.
|
|
5
|
+
"@util.js/errors": "0.41.7"
|
|
6
6
|
},
|
|
7
7
|
"description": "JavaScript utility class for private member variables",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"index.js"
|
|
11
|
+
],
|
|
8
12
|
"keywords": [
|
|
9
13
|
"field",
|
|
10
14
|
"fields",
|
|
@@ -29,5 +33,5 @@
|
|
|
29
33
|
"type": "git",
|
|
30
34
|
"url": "https://github.com/creemama/utiljs/tree/master/packages/utiljs-privates"
|
|
31
35
|
},
|
|
32
|
-
"version": "0.41.
|
|
36
|
+
"version": "0.41.7"
|
|
33
37
|
}
|
package/lib/Privates.js
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const RethrownError = require("@util.js/errors/RethrownError");
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* JavaScript utility class for private member variables.
|
|
7
|
-
*
|
|
8
|
-
* This class decreases some of the boilerplate of the
|
|
9
|
-
* [WeakMap]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap}
|
|
10
|
-
* pattern as described by
|
|
11
|
-
* ["Private Variables in JavaScript with ES6 WeakMaps"]{@link https://modernweb.com/private-variables-in-javascript-with-es6-weakmaps/}
|
|
12
|
-
* and
|
|
13
|
-
* ["Privates In ES2015 Javascript Classes"]{@link https://ilikekillnerds.com/2015/09/privates-in-es2015-javascript-classes/}.
|
|
14
|
-
* This utility handles shallow copying at construction.
|
|
15
|
-
*
|
|
16
|
-
* Private fields may become a part of the ECMA standard. The following is a
|
|
17
|
-
* StackOverflow answer to the question
|
|
18
|
-
* ["Private properties in JavaScript ES6 classes"]{@link https://stackoverflow.com/a/52237988}:
|
|
19
|
-
* "Private fields are being implemented in the ECMA standard. You can start
|
|
20
|
-
* using them today with babel 7 and stage 3 preset." Wouldn't private fields
|
|
21
|
-
* make this class obsolete? Not necessarily. If each of your privates is a
|
|
22
|
-
* function (e.g., you want to lazy load all member variables), this class has
|
|
23
|
-
* convenience methods for that.
|
|
24
|
-
*
|
|
25
|
-
* @public
|
|
26
|
-
* @class Privates
|
|
27
|
-
*/
|
|
28
|
-
module.exports = class Privates {
|
|
29
|
-
constructor() {
|
|
30
|
-
this.privates = new WeakMap();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Calls `this.get(thiz, property)()`.
|
|
35
|
-
*
|
|
36
|
-
* @public
|
|
37
|
-
* @function Privates#call
|
|
38
|
-
*/
|
|
39
|
-
call(thiz, property) {
|
|
40
|
-
try {
|
|
41
|
-
// this.get(thiz, property)() does not work all the time unless we
|
|
42
|
-
// call bind, which we do in the set method. See
|
|
43
|
-
// https://javascript.info/object-methods.
|
|
44
|
-
return this.get(thiz, property)();
|
|
45
|
-
} catch (e) {
|
|
46
|
-
const propertyStr = getPropertyErrorString(property);
|
|
47
|
-
throw new RethrownError(
|
|
48
|
-
e,
|
|
49
|
-
`privates.call(${thiz}, ${propertyStr}) failed. ${e.message}`,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
get(thiz, property) {
|
|
55
|
-
try {
|
|
56
|
-
return this.getProps(thiz)[property];
|
|
57
|
-
} catch (e) {
|
|
58
|
-
const propertyStr = getPropertyErrorString(property);
|
|
59
|
-
throw new RethrownError(
|
|
60
|
-
e,
|
|
61
|
-
`privates.get(${thiz}, ${propertyStr}) failed. ${e.message}`,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
getCallProxy(thiz) {
|
|
67
|
-
return new Proxy(this.getProps(thiz), { get: callProp });
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
getProps(thiz) {
|
|
71
|
-
try {
|
|
72
|
-
let properties = this.privates.get(thiz);
|
|
73
|
-
if (!properties) this.privates.set(thiz, (properties = {}));
|
|
74
|
-
return properties;
|
|
75
|
-
} catch (e) {
|
|
76
|
-
if (thiz == null)
|
|
77
|
-
throw new RethrownError(e, `thiz cannot be null or undefined.`);
|
|
78
|
-
throw e;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
lambdaize(properties) {
|
|
83
|
-
try {
|
|
84
|
-
const lambdaProperties = {};
|
|
85
|
-
Object.entries(properties).forEach(
|
|
86
|
-
([property, value]) => (lambdaProperties[property] = () => value),
|
|
87
|
-
);
|
|
88
|
-
return lambdaProperties;
|
|
89
|
-
} catch (e) {
|
|
90
|
-
if (properties == null)
|
|
91
|
-
throw new RethrownError(e, `properties cannot be null or undefined.`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
lazyLoad(thiz, property, lazyLoadFunction) {
|
|
96
|
-
if (typeof lazyLoadFunction !== "function")
|
|
97
|
-
throw new TypeError(
|
|
98
|
-
`lazyLoadFunction (${lazyLoadFunction}) must be a function.`,
|
|
99
|
-
);
|
|
100
|
-
let value;
|
|
101
|
-
let loaded = false;
|
|
102
|
-
this.set(thiz, property, () => {
|
|
103
|
-
if (!loaded) {
|
|
104
|
-
value = lazyLoadFunction();
|
|
105
|
-
loaded = true;
|
|
106
|
-
}
|
|
107
|
-
return value;
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
lazyLoadProps(thiz, ...lazilyLoadedProperties) {
|
|
112
|
-
const t = this;
|
|
113
|
-
lazilyLoadedProperties.forEach((currentProperties) => {
|
|
114
|
-
Object.entries(currentProperties).forEach(
|
|
115
|
-
([property, lazyLoadFunction]) =>
|
|
116
|
-
t.lazyLoad(thiz, property, lazyLoadFunction),
|
|
117
|
-
);
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
set(thiz, property, newValue) {
|
|
122
|
-
try {
|
|
123
|
-
this.getProps(thiz)[property] = newValue;
|
|
124
|
-
} catch (e) {
|
|
125
|
-
const propertyStr = getPropertyErrorString(property);
|
|
126
|
-
throw new RethrownError(
|
|
127
|
-
e,
|
|
128
|
-
`privates.set(${thiz}, ${propertyStr}, ${newValue}) failed. ${e.message}`,
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
setProps(thiz, ...properties) {
|
|
134
|
-
const t = this;
|
|
135
|
-
properties.forEach((currentProperties) => {
|
|
136
|
-
Object.entries(currentProperties).forEach(([property, value]) =>
|
|
137
|
-
t.set(thiz, property, value),
|
|
138
|
-
);
|
|
139
|
-
getAllMethods(currentProperties).forEach((method) =>
|
|
140
|
-
t.set(thiz, method, currentProperties[method].bind(currentProperties)),
|
|
141
|
-
);
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
subset(thiz, ...properties) {
|
|
146
|
-
const object = {};
|
|
147
|
-
for (let i = 0; i < properties.length; i++)
|
|
148
|
-
object[properties[i]] = this.get(thiz, properties[i]);
|
|
149
|
-
return object;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
function getPropertyErrorString(property) {
|
|
154
|
-
// https://stackoverflow.com/a/7772724
|
|
155
|
-
if (typeof property === "string" || property instanceof String)
|
|
156
|
-
return `"${property}"`;
|
|
157
|
-
return `${property}`;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function callProp(obj, property) {
|
|
161
|
-
try {
|
|
162
|
-
return obj[property]();
|
|
163
|
-
} catch (e) {
|
|
164
|
-
throw new RethrownError(e, `Calling "${property}" as a function failed.`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class
|
|
169
|
-
function getAllMethods(obj) {
|
|
170
|
-
if (!Object.getPrototypeOf(obj)) return [];
|
|
171
|
-
|
|
172
|
-
let props = [];
|
|
173
|
-
|
|
174
|
-
do {
|
|
175
|
-
const l = Object.getOwnPropertyNames(obj)
|
|
176
|
-
.concat(Object.getOwnPropertySymbols(obj).map((s) => s.toString()))
|
|
177
|
-
.sort()
|
|
178
|
-
.filter(
|
|
179
|
-
(p, i, arr) =>
|
|
180
|
-
typeof obj[p] === "function" && // only the methods
|
|
181
|
-
p !== "constructor" && // not the constructor
|
|
182
|
-
(i === 0 || p !== arr[i - 1]) && // not overriding in this prototype
|
|
183
|
-
props.indexOf(p) === -1, // not overridden in a child
|
|
184
|
-
);
|
|
185
|
-
props = props.concat(l);
|
|
186
|
-
} while (
|
|
187
|
-
(obj = Object.getPrototypeOf(obj)) && // walk-up the prototype chain
|
|
188
|
-
Object.getPrototypeOf(obj) // not the the Object prototype methods (hasOwnProperty, etc...)
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
return props;
|
|
192
|
-
}
|
package/test/PrivatesTest.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const { expect } = require("chai");
|
|
4
|
-
const Privates = require("..");
|
|
5
|
-
const { RethrownError } = require("@util.js/errors");
|
|
6
|
-
|
|
7
|
-
const ExampleUsingGet = (function () {
|
|
8
|
-
const privates = new Privates();
|
|
9
|
-
|
|
10
|
-
class ExampleUsingGet {
|
|
11
|
-
constructor(options) {
|
|
12
|
-
privates.setProps(this, options);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
getProperty1() {
|
|
16
|
-
return property1(this);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
getProperty2() {
|
|
20
|
-
return property2(this);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function get(thiz, privatePart) {
|
|
25
|
-
return privates.get(thiz, privatePart);
|
|
26
|
-
}
|
|
27
|
-
function property1(thiz) {
|
|
28
|
-
return get(thiz, "property1");
|
|
29
|
-
}
|
|
30
|
-
function property2(thiz) {
|
|
31
|
-
return get(thiz, "property2");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return ExampleUsingGet;
|
|
35
|
-
})();
|
|
36
|
-
|
|
37
|
-
const ExampleUsingCall = (function () {
|
|
38
|
-
const privates = new Privates();
|
|
39
|
-
|
|
40
|
-
class ExampleUsingCall {
|
|
41
|
-
constructor(options) {
|
|
42
|
-
privates.setProps(this, options);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
getProperty1() {
|
|
46
|
-
return property1(this);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
getProperty2() {
|
|
50
|
-
return property2(this);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function call(thiz, privatePart) {
|
|
55
|
-
return privates.call(thiz, privatePart);
|
|
56
|
-
}
|
|
57
|
-
function property1(thiz) {
|
|
58
|
-
return call(thiz, "property1");
|
|
59
|
-
}
|
|
60
|
-
function property2(thiz) {
|
|
61
|
-
return call(thiz, "property2");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return ExampleUsingCall;
|
|
65
|
-
})();
|
|
66
|
-
|
|
67
|
-
describe("Privates", () => {
|
|
68
|
-
it("should hide member variables using Privates#get", () => {
|
|
69
|
-
const example = new ExampleUsingGet({
|
|
70
|
-
property1: "a",
|
|
71
|
-
property2: 2,
|
|
72
|
-
});
|
|
73
|
-
expect(example.property1).to.be.undefined;
|
|
74
|
-
expect(example.property2).to.be.undefined;
|
|
75
|
-
expect(example.getProperty1()).to.eql("a");
|
|
76
|
-
expect(example.getProperty2()).to.eql(2);
|
|
77
|
-
});
|
|
78
|
-
it("should hide member variables using Privates#call", () => {
|
|
79
|
-
const example = new ExampleUsingCall({
|
|
80
|
-
property1: () => "a",
|
|
81
|
-
property2: () => 2,
|
|
82
|
-
});
|
|
83
|
-
expect(example.property1).to.be.undefined;
|
|
84
|
-
expect(example.property2).to.be.undefined;
|
|
85
|
-
expect(example.getProperty1()).to.eql("a");
|
|
86
|
-
expect(example.getProperty2()).to.eql(2);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe("Privates#call", () => {
|
|
91
|
-
const privates = new Privates();
|
|
92
|
-
it("should correctly call privates that are functions", () => {
|
|
93
|
-
const thiz = {};
|
|
94
|
-
|
|
95
|
-
// Test a lambda function.
|
|
96
|
-
privates.set(thiz, "lambda", () => "lambdaObj");
|
|
97
|
-
expect(privates.call(thiz, "lambda")).to.eql("lambdaObj");
|
|
98
|
-
|
|
99
|
-
// Test a declared function.
|
|
100
|
-
function func() {
|
|
101
|
-
return "funcObj";
|
|
102
|
-
}
|
|
103
|
-
privates.set(thiz, "function", func);
|
|
104
|
-
expect(privates.call(thiz, "function")).to.eql("funcObj");
|
|
105
|
-
|
|
106
|
-
// Test a class function.
|
|
107
|
-
class CallClass {
|
|
108
|
-
delegate() {
|
|
109
|
-
return "classObj";
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
call() {
|
|
113
|
-
return this.delegate();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const c = new CallClass();
|
|
117
|
-
privates.set(thiz, "classBound", c.call.bind(c));
|
|
118
|
-
|
|
119
|
-
// Test an object as a property.
|
|
120
|
-
const key = { a: "a" };
|
|
121
|
-
privates.set(thiz, key, () => "objectAsProperty");
|
|
122
|
-
expect(privates.call(thiz, key)).to.eql("objectAsProperty");
|
|
123
|
-
|
|
124
|
-
// Test a symbol as a property.
|
|
125
|
-
const symbol = Symbol("symbol");
|
|
126
|
-
privates.set(thiz, symbol, () => "symbolAsProperty");
|
|
127
|
-
expect(privates.call(thiz, symbol)).to.eql("symbolAsProperty");
|
|
128
|
-
|
|
129
|
-
// Test a number as a property.
|
|
130
|
-
privates.set(thiz, -5, () => "numberAsProperty");
|
|
131
|
-
expect(privates.call(thiz, -5)).to.eql("numberAsProperty");
|
|
132
|
-
|
|
133
|
-
// Test a boolean as a property.
|
|
134
|
-
privates.set(thiz, false, () => "booleanAsProperty");
|
|
135
|
-
expect(privates.call(thiz, false)).to.eql("booleanAsProperty");
|
|
136
|
-
|
|
137
|
-
// Test undefined as a property.
|
|
138
|
-
privates.set(thiz, undefined, () => "undefinedAsProperty");
|
|
139
|
-
expect(privates.call(thiz, undefined)).to.eql("undefinedAsProperty");
|
|
140
|
-
|
|
141
|
-
// Test null as a property.
|
|
142
|
-
privates.set(thiz, null, () => "nullAsProperty");
|
|
143
|
-
expect(privates.call(thiz, null)).to.eql("nullAsProperty");
|
|
144
|
-
});
|
|
145
|
-
it("should fail with invalid arguments", () => {
|
|
146
|
-
const thiz = {};
|
|
147
|
-
|
|
148
|
-
expect(() => privates.call()).to.throw(RethrownError);
|
|
149
|
-
// eslint-disable-next-line no-useless-call
|
|
150
|
-
expect(() => privates.call(null, "nonexistent")).to.throw(RethrownError);
|
|
151
|
-
expect(() => privates.call(thiz)).to.throw(RethrownError);
|
|
152
|
-
expect(() => privates.call(thiz, "nonexistent")).to.throw(RethrownError);
|
|
153
|
-
|
|
154
|
-
// Test an unbound function failing.
|
|
155
|
-
class CallClass {
|
|
156
|
-
delegate() {
|
|
157
|
-
return "classObj";
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
call() {
|
|
161
|
-
return this.delegate();
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
const c = new CallClass();
|
|
165
|
-
privates.set(thiz, "classBound", c.call);
|
|
166
|
-
expect(() => privates.call(thiz, "classBound")).to.throw(RethrownError);
|
|
167
|
-
|
|
168
|
-
// Test a private that is not a function.
|
|
169
|
-
privates.set(thiz, "notafunction", "string");
|
|
170
|
-
expect(() => privates.call(thiz, "notafunction")).to.throw(RethrownError);
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
describe("Privates#lambdaize", () => {
|
|
175
|
-
const privates = new Privates();
|
|
176
|
-
it("should convert an object's properties to lambda properties", () => {
|
|
177
|
-
const obj = privates.lambdaize({
|
|
178
|
-
a: 0,
|
|
179
|
-
b: 1,
|
|
180
|
-
});
|
|
181
|
-
expect(obj.a()).to.eql(0);
|
|
182
|
-
expect(obj.b()).to.eql(1);
|
|
183
|
-
|
|
184
|
-
expect(privates.lambdaize(true)).to.eql({});
|
|
185
|
-
expect(privates.lambdaize(1)).to.eql({});
|
|
186
|
-
expect(Object.keys(privates.lambdaize("string"))).to.eql([
|
|
187
|
-
"0",
|
|
188
|
-
"1",
|
|
189
|
-
"2",
|
|
190
|
-
"3",
|
|
191
|
-
"4",
|
|
192
|
-
"5",
|
|
193
|
-
]);
|
|
194
|
-
expect(privates.lambdaize(Symbol("string"))).to.eql({});
|
|
195
|
-
expect(privates.lambdaize({})).to.eql({});
|
|
196
|
-
});
|
|
197
|
-
it("should fail with invalid arguments", () => {
|
|
198
|
-
expect(() => privates.lambdaize()).to.throw(RethrownError);
|
|
199
|
-
expect(() => privates.lambdaize(null)).to.throw(RethrownError);
|
|
200
|
-
expect(() => privates.lambdaize(undefined)).to.throw(RethrownError);
|
|
201
|
-
});
|
|
202
|
-
});
|