cfprotected 1.1.3 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +11 -1
  2. package/index.mjs +54 -30
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -104,12 +104,22 @@ class Example {
104
104
  As a bonus, when used on the constructor, it creates an additional property named "cla\$\$" on the prototype. This "cla\$\$" property gives instances a means to reference the class constructor even when the class itself is anonymous. This is a fill-in feature for one of the TC39 proposals offering syntax for the same.
105
105
 
106
106
  ## **abstract(klass)**
107
- This method is a class wrapper that prevents instances of the class from being constructed directly. To construct an instance of the class you must extend it. This should be nearly identical to the same functionality that exists in some compiled languages.
107
+ This method has 2 different uses. The first is as a class wrapper that prevents instances of the class from being constructed directly. To construct an instance of the class, you must extend it. This should be nearly identical to the same functionality that exists in some compiled languages.
108
108
  ```js
109
109
  const Example = abstract(class {
110
110
  ...
111
111
  });
112
112
  ```
113
+ The second use is to mark the function as not having a valid implementation. Such functions must be overridden by derived classes.
114
+ ```js
115
+ class Example {
116
+ ...
117
+ #prot = share(this, Example, {
118
+ method = abstract("Example::method")
119
+ });
120
+ ...
121
+ };
122
+ ```
113
123
 
114
124
  ## **final(klass)**
115
125
  This method is a class wrapper that prevents instances of the class from being created using descendant classes. It also attempts to prevent creation of descendant classes.
package/index.mjs CHANGED
@@ -39,6 +39,8 @@ function bindDescriptor(desc, context) {
39
39
  * @returns {Object} The fully constructed inheritance object.
40
40
  */
41
41
  function share(inst, klass, members) {
42
+ let retval = {};
43
+
42
44
  if ((typeof(inst) == "function")
43
45
  && klass && (typeof(klass) == "object")
44
46
  && (members === void 0)) {
@@ -67,7 +69,7 @@ function share(inst, klass, members) {
67
69
  * }
68
70
  */
69
71
 
70
- //Find the nearest known registered ancestor
72
+ //Find the nearest known registered ancestor class
71
73
  let ancestor = Object.getPrototypeOf(klass);
72
74
  while (ancestor && !memos.has(ancestor)) {
73
75
  ancestor = Object.getPrototypeOf(ancestor);
@@ -76,25 +78,22 @@ function share(inst, klass, members) {
76
78
  //Get the memo from that ancestor
77
79
  let ancestorMemo = memos.get(ancestor) || new WeakMap();
78
80
 
79
- //Create a memo for the current class
81
+ //Create a memo map for the current class
80
82
  if (!memos.has(klass)) {
81
83
  memos.set(klass, new WeakMap());
82
84
  }
83
85
 
84
86
  //Get the protected data object.
85
87
  let ancestorKey = (inst === klass) ? ancestor : inst;
86
- let memo = ancestorMemo.get(ancestorKey) || {
87
- data: {},
88
- $uper: {},
89
- };
90
- let retval = {};
88
+ let memo = ancestorMemo.get(ancestorKey) || {data: {}, $uper: {}, inheritance: null};
89
+ let protData = memo.data;
91
90
 
92
91
  //Get the details of the protected properties.
93
92
  let mDesc = Object.getOwnPropertyDescriptors(members);
94
93
  let mKeys = getAllOwnKeys(members);
95
94
 
96
- //Change the prototype of protoData using the new members.
97
- let prototype = Object.getPrototypeOf(memo.data);
95
+ //Add the new members to the prototype chain of protData.
96
+ let prototype = Object.getPrototypeOf(protData);
98
97
  let proto = Object.create(prototype,
99
98
  Object.fromEntries(mKeys
100
99
  .map(k => {
@@ -108,11 +107,18 @@ function share(inst, klass, members) {
108
107
  bindDescriptor(mDesc[k], inst);
109
108
  return [k, mDesc[k]];
110
109
  })));
111
- Object.setPrototypeOf(retval, proto);
110
+ Object.setPrototypeOf(protData, proto);
112
111
 
112
+ //Build the accessors for this class.
113
+ mKeys.forEach(m => {
114
+ Object.defineProperty(retval, m, {
115
+ get() { return protData[m]; },
116
+ set(v) { protData[m] = v; }
117
+ });
118
+ });
119
+
113
120
  //Define the "$uper" accessors
114
- let $uper = {};
115
- Object.defineProperty(retval, "$uper", { value: $uper });
121
+ Object.defineProperty(retval, "$uper", { value: {} });
116
122
 
117
123
  //Build up the "$uper" object
118
124
  for (let key of mKeys) {
@@ -121,23 +127,27 @@ function share(inst, klass, members) {
121
127
  while (!obj.hasOwnProperty(key)) {
122
128
  obj = Object.getPrototypeOf(obj);
123
129
  }
124
- Object.defineProperty($uper, key, Object.getOwnPropertyDescriptor(obj, key));
130
+ Object.defineProperty(retval.$uper, key, Object.getOwnPropertyDescriptor(obj, key));
125
131
  }
126
132
  }
127
133
 
128
134
  //Attach the super inheritance
129
- Object.setPrototypeOf($uper, memo.$uper);
135
+ Object.setPrototypeOf(retval.$uper, memo.$uper);
136
+
137
+ //Inherit the inheritance
138
+ Object.setPrototypeOf(retval, memo.inheritance);
130
139
 
131
140
  //Save the inheritance & protected data
132
141
  memos.get(klass).set(inst, {
133
- data: retval,
134
- $uper: $uper
142
+ data: protData,
143
+ inheritance: retval,
144
+ $uper: retval.$uper
135
145
  });
136
146
 
137
147
  return retval;
138
148
  }
139
149
 
140
- /**
150
+ /**
141
151
  * Binds the class instance to itself to allow code to selectively avoid Proxy
142
152
  * issues, especially ones involving private fields. Also binds the class
143
153
  * constructor to the instance for static referencing as "cla$$".
@@ -173,23 +183,37 @@ function accessor(desc) {
173
183
  /**
174
184
  * A class wrapper that blocks construction of an instance if the class being
175
185
  * instantiated is not a descendant of the current class.
176
- * @param {Function} klass The constructor of the current class.
186
+ * @param {function|string} klass If a function, the constructor of the current
187
+ * class. If a string, the name of the function being abstracted.
188
+ * @returns {function} Either an extended class that denies direct construction
189
+ * or a function that immediately throws.
177
190
  */
178
191
  function abstract(klass) {
179
- let name = klass.name?klass.name : "";
180
- let retval = class extends klass {
181
- constructor (...args) {
182
- if (new.target === retval) {
183
- throw new TypeError(`Class constructor ${name} is abstract and cannot be directly invoked with 'new'`);
192
+ let retval;
193
+ if (typeof(klass) == "function") {
194
+ let name = klass.name?klass.name : "";
195
+ retval = class extends klass {
196
+ constructor (...args) {
197
+ if (new.target === retval) {
198
+ throw new TypeError(`Class constructor ${name} is abstract and cannot be directly invoked with 'new'`);
199
+ }
200
+ super(...args);
184
201
  }
185
- super(...args);
186
- }
187
- };
202
+ };
188
203
 
189
- if (memos.has(klass)) {
190
- let memo = memos.get(klass);
191
- memos.set(retval, memo);
192
- memo.set(retval, memo.get(klass));
204
+ if (memos.has(klass)) {
205
+ let memo = memos.get(klass);
206
+ memos.set(retval, memo);
207
+ memo.set(retval, memo.get(klass));
208
+ }
209
+ }
210
+ else if (typeof(klass) == "string") {
211
+ retval = function() {
212
+ throw new TypeError(`${klass}() must be overridden`);
213
+ }
214
+ }
215
+ else {
216
+ throw new TypeError(`abstract parameter must be a function or string`)
193
217
  }
194
218
 
195
219
  return retval;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cfprotected",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "An implementation of protected fields on top of class fields.",
5
5
  "main": "index.js",
6
6
  "scripts": {