nv-facutil-has 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 ADDED
@@ -0,0 +1,417 @@
1
+ # has - JavaScript Property Inspector
2
+
3
+ A comprehensive utility for checking and inspecting object properties and prototype chains in JavaScript.
4
+
5
+ ## Installation
6
+
7
+ ```javascript
8
+ const has = require('nv-facutil-has');
9
+ ```
10
+
11
+ ## ⚠️ Important Usage Note
12
+
13
+ **The main function `has()` is designed for production use. All other utility functions are intended for CLI tools, testing frameworks, and debugging/inspection purposes only.**
14
+
15
+ These inspection utilities provide deep introspection capabilities that are useful for:
16
+ - Interactive REPL/CLI environments
17
+ - Test assertions and debugging
18
+ - Object structure analysis
19
+ - Educational purposes
20
+
21
+ They are **not recommended** for production application logic due to performance considerations and complexity.
22
+
23
+ ---
24
+
25
+ ## Main Function (Production Use)
26
+
27
+ ### `has(object, key, only_own=false)`
28
+
29
+ Checks if an object has a specific property.
30
+
31
+ **Parameters:**
32
+ - `object`: The object to check
33
+ - `key`: The property key (string or symbol)
34
+ - `only_own` (default: `false`): If `true`, checks only own properties; if `false`, includes prototype chain
35
+
36
+ **Returns:** `boolean`
37
+
38
+ **Examples:**
39
+
40
+ ```javascript
41
+ const obj = { a: 1, b: 2 };
42
+
43
+ // Check with prototype chain
44
+ has(obj, 'a'); // true
45
+ has(obj, 'toString'); // true (inherited from Object.prototype)
46
+
47
+ // Check only own properties
48
+ has(obj, 'a', true); // true
49
+ has(obj, 'toString', true); // false
50
+
51
+ // Safe with null/undefined
52
+ has(null, 'prop'); // false
53
+ has(undefined, 'prop'); // false
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Inspection Utilities (CLI/Testing Only)
59
+
60
+ ### `slow(object, key, dig=Infinity)`
61
+
62
+ Searches for a property within a specified depth range in the prototype chain.
63
+
64
+ **Parameters:**
65
+ - `object`: The object to search
66
+ - `key`: The property key to find
67
+ - `dig` (default: `Infinity`): Maximum depth to search
68
+ - `0`: Own properties only
69
+ - `n`: Search up to n levels in prototype chain
70
+ - `Infinity`: Search entire chain
71
+
72
+ **Returns:** `boolean`
73
+
74
+ **Examples:**
75
+
76
+ ```javascript
77
+ const grandparent = { a: 1 };
78
+ const parent = Object.create(grandparent);
79
+ parent.b = 2;
80
+ const child = Object.create(parent);
81
+ child.c = 3;
82
+
83
+ has.slow(child, 'c', 0); // true (own property)
84
+ has.slow(child, 'b', 0); // false (not own)
85
+ has.slow(child, 'b', 1); // true (within 1 level)
86
+ has.slow(child, 'a', 2); // true (within 2 levels)
87
+ has.slow(child, 'a', 1); // false (need 2 levels)
88
+ has.slow(child, 'a', Infinity); // true (search all)
89
+ ```
90
+
91
+ ---
92
+
93
+ ### `exact(object, key, depth=Infinity)`
94
+
95
+ Checks if a property exists at an exact depth in the prototype chain.
96
+
97
+ **Parameters:**
98
+ - `object`: The object to check
99
+ - `key`: The property key
100
+ - `depth`: Exact prototype chain depth
101
+ - `0`: Object's own property
102
+ - `n`: Property at exactly n levels up
103
+ - `Infinity`: Anywhere in chain
104
+
105
+ **Returns:** `boolean`
106
+
107
+ **Examples:**
108
+
109
+ ```javascript
110
+ const gp = { a: 1 };
111
+ const p = Object.create(gp);
112
+ p.b = 2;
113
+ const c = Object.create(p);
114
+ c.c = 3;
115
+
116
+ has.exact(c, 'c', 0); // true (own property)
117
+ has.exact(c, 'b', 0); // false (at level 1)
118
+ has.exact(c, 'b', 1); // true (exactly at level 1)
119
+ has.exact(c, 'a', 1); // false (at level 2, not 1)
120
+ has.exact(c, 'a', 2); // true (exactly at level 2)
121
+ ```
122
+
123
+ ---
124
+
125
+ ### `get_proto_chain(object)`
126
+
127
+ Returns the complete prototype chain as an array.
128
+
129
+ **Parameters:**
130
+ - `object`: The object to inspect
131
+
132
+ **Returns:** `Array` - Array of objects in the prototype chain
133
+
134
+ **Examples:**
135
+
136
+ ```javascript
137
+ const parent = { x: 1 };
138
+ const child = Object.create(parent);
139
+ child.y = 2;
140
+
141
+ const chain = has.get_proto_chain(child);
142
+ // [child, parent, Object.prototype]
143
+
144
+ console.log(chain.length); // 3
145
+ console.log(chain[0] === child); // true
146
+ console.log(chain[1] === parent); // true
147
+ ```
148
+
149
+ ---
150
+
151
+ ### `get_prop_descs(object, split_layer=false)`
152
+
153
+ Retrieves property descriptors from the prototype chain.
154
+
155
+ **Parameters:**
156
+ - `object`: The object to inspect
157
+ - `split_layer` (default: `false`): Output format
158
+ - `false`: Single object with all descriptors merged
159
+ - `true`: Array of `{object, descriptors}` per layer
160
+
161
+ **Returns:** `Object` or `Array`
162
+
163
+ **Examples:**
164
+
165
+ ```javascript
166
+ const obj = { a: 1 };
167
+ Object.defineProperty(obj, 'b', {
168
+ value: 2,
169
+ writable: false,
170
+ enumerable: true
171
+ });
172
+
173
+ // Merged format (default)
174
+ const descs = has.get_prop_descs(obj, false);
175
+ console.log(descs.a);
176
+ // { value: 1, writable: true, enumerable: true, configurable: true }
177
+
178
+ console.log(descs.b.writable); // false
179
+
180
+ // Layer-by-layer format
181
+ const layers = has.get_prop_descs(obj, true);
182
+ layers.forEach((layer, i) => {
183
+ console.log(`Layer ${i}:`, Object.keys(layer.descriptors));
184
+ });
185
+ ```
186
+
187
+ ---
188
+
189
+ ### `get_all_keys(object, split_layer=false, enable_grouping=false)`
190
+
191
+ Gets all property keys (including symbols and non-enumerable) from the prototype chain.
192
+
193
+ **Parameters:**
194
+ - `object`: The object to inspect
195
+ - `split_layer` (default: `false`): Separate by prototype layers
196
+ - `enable_grouping` (default: `false`): Group by property characteristics
197
+
198
+ **Returns:** `Array` or `Object`
199
+
200
+ **Property Signature Format (when `enable_grouping=true`):**
201
+ - `k-__v-wec`: string key, data property, writable+enumerable+configurable
202
+ - `k-g__-_ec`: string key, getter only, enumerable+configurable
203
+ - `k-gs_-_e_`: string key, getter+setter, enumerable only
204
+ - `y-__v-wec`: symbol key, data property, writable+enumerable+configurable
205
+
206
+ Format: `{keyType}-{propType}-{flags}`
207
+ - Key type: `k` (string) or `y` (symbol)
208
+ - Property type: `__v` (data), `g__` (getter), `_s_` (setter), `gs_` (both)
209
+ - Flags: `w` (writable), `e` (enumerable), `c` (configurable), `_` (absent)
210
+
211
+ **Examples:**
212
+
213
+ ```javascript
214
+ const obj = { a: 1 };
215
+ Object.defineProperty(obj, 'getter', {
216
+ get: () => 'value',
217
+ enumerable: true
218
+ });
219
+ const sym = Symbol('test');
220
+ obj[sym] = 'symbol value';
221
+
222
+ // Simple list
223
+ const keys = has.get_all_keys(obj);
224
+ console.log(keys); // ['a', 'getter', Symbol(test)]
225
+
226
+ // Grouped by characteristics
227
+ const grouped = has.get_all_keys(obj, false, true);
228
+ console.log(grouped);
229
+ // {
230
+ // 'k-__v-wec': ['a'],
231
+ // 'k-g__-_ec': ['getter'],
232
+ // 'y-__v-wec': [Symbol(test)]
233
+ // }
234
+
235
+ // Split by layers
236
+ const parent = { x: 10 };
237
+ const child = Object.create(parent);
238
+ child.y = 20;
239
+
240
+ const layers = has.get_all_keys(child, true);
241
+ console.log(layers);
242
+ // [
243
+ // ['y'], // Layer 0: child's own
244
+ // ['x'] // Layer 1: parent's own
245
+ // ]
246
+ ```
247
+
248
+ ---
249
+
250
+ ### `nestify(object)`
251
+
252
+ Converts a flat object into a nested prototype chain structure.
253
+
254
+ **Parameters:**
255
+ - `object`: A flat object to convert
256
+
257
+ **Returns:** Object with properties distributed across prototype chain
258
+
259
+ **How it works:**
260
+ - First property → deepest prototype
261
+ - Middle properties → intermediate prototypes
262
+ - Last property → object's own property
263
+
264
+ **Examples:**
265
+
266
+ ```javascript
267
+ const flat = { a: 100, b: 200, c: 300 };
268
+ const nested = has.nestify(flat);
269
+
270
+ // Resulting structure:
271
+ // nested (own): { c: 300 }
272
+ // └─ prototype (empty layer)
273
+ // └─ prototype: { b: 200 }
274
+ // └─ prototype: { a: 100 }
275
+
276
+ console.log(nested.c); // 300 (own property)
277
+ console.log(nested.b); // 200 (from prototype)
278
+ console.log(nested.a); // 100 (from deeper prototype)
279
+
280
+ console.log(Object.hasOwn(nested, 'c')); // true
281
+ console.log(Object.hasOwn(nested, 'b')); // false
282
+ console.log(Object.hasOwn(nested, 'a')); // false
283
+
284
+ // Verify depths
285
+ has.exact(nested, 'c', 0); // true
286
+ has.exact(nested, 'b', 2); // true
287
+ has.exact(nested, 'a', 3); // true
288
+ ```
289
+
290
+ ---
291
+
292
+ ### `flatify(object)`
293
+
294
+ Converts a nested prototype chain back into a flat object.
295
+
296
+ **Parameters:**
297
+ - `object`: Object with prototype chain to flatten
298
+
299
+ **Returns:** Flat object with all properties merged
300
+
301
+ **Examples:**
302
+
303
+ ```javascript
304
+ const nested = has.nestify({ a: 1, b: 2, c: 3 });
305
+ const flat = has.flatify(nested);
306
+
307
+ console.log(flat); // { c: 3, b: 2, a: 1 }
308
+
309
+ // All values preserved
310
+ console.log(flat.a === 1); // true
311
+ console.log(flat.b === 2); // true
312
+ console.log(flat.c === 3); // true
313
+
314
+ // Round-trip preservation
315
+ const original = { x: 10, y: 20, z: 30 };
316
+ const restored = has.flatify(has.nestify(original));
317
+ // restored contains all original properties and values
318
+ ```
319
+
320
+ ---
321
+
322
+ ## Use Cases
323
+
324
+ ### Production Code
325
+
326
+ ```javascript
327
+ // Simple property existence check
328
+ if (has(config, 'database')) {
329
+ // Use config.database
330
+ }
331
+
332
+ // Check own properties only
333
+ if (has(userInput, 'password', true)) {
334
+ // Process password
335
+ }
336
+ ```
337
+
338
+ ### Testing & Debugging
339
+
340
+ ```javascript
341
+ // In a test framework
342
+ describe('Object structure', () => {
343
+ it('should have property at correct depth', () => {
344
+ const obj = createComplexObject();
345
+ assert(has.exact(obj, 'deepProp', 3));
346
+ });
347
+
348
+ it('should have all expected keys', () => {
349
+ const keys = has.get_all_keys(obj, false, true);
350
+ assert(keys['k-__v-wec'].includes('importantProp'));
351
+ });
352
+ });
353
+
354
+ // In CLI inspection tool
355
+ function inspect(obj) {
356
+ const chain = has.get_proto_chain(obj);
357
+ console.log(`Prototype chain depth: ${chain.length}`);
358
+
359
+ const descs = has.get_prop_descs(obj, true);
360
+ descs.forEach((layer, i) => {
361
+ console.log(`Level ${i}:`, Object.keys(layer.descriptors));
362
+ });
363
+ }
364
+ ```
365
+
366
+ ### Object Analysis
367
+
368
+ ```javascript
369
+ // Analyze object structure
370
+ function analyzeObject(obj) {
371
+ const allKeys = has.get_all_keys(obj, false, true);
372
+
373
+ console.log('Property distribution:');
374
+ for (const [sig, keys] of Object.entries(allKeys)) {
375
+ console.log(`${sig}: ${keys.length} properties`);
376
+ }
377
+
378
+ const layers = has.get_proto_chain(obj);
379
+ console.log(`\nPrototype depth: ${layers.length}`);
380
+ }
381
+ ```
382
+
383
+ ---
384
+
385
+ ## Performance Considerations
386
+
387
+ **Main function `has()`:**
388
+ - Optimized for production use
389
+ - O(1) for own properties
390
+ - O(n) for prototype chain (where n = chain depth)
391
+
392
+ **Inspection utilities:**
393
+ - May perform multiple traversals
394
+ - Use reflection APIs (getOwnPropertyDescriptors, etc.)
395
+ - Not optimized for high-frequency calls
396
+ - Best suited for development/debugging scenarios
397
+
398
+ ---
399
+
400
+ ## API Summary
401
+
402
+ | Function | Purpose | Production Use |
403
+ |----------|---------|----------------|
404
+ | `has(o, key, only_own)` | Check property existence | ✅ Yes |
405
+ | `slow(o, key, dig)` | Search within depth range | ❌ No (CLI/Testing) |
406
+ | `exact(o, key, depth)` | Check exact depth | ❌ No (CLI/Testing) |
407
+ | `get_proto_chain(o)` | Get prototype chain | ❌ No (CLI/Testing) |
408
+ | `get_prop_descs(o, split)` | Get property descriptors | ❌ No (CLI/Testing) |
409
+ | `get_all_keys(o, split, group)` | Get all keys with details | ❌ No (CLI/Testing) |
410
+ | `nestify(o)` | Flatten to chain | ❌ No (CLI/Testing) |
411
+ | `flatify(o)` | Chain to flat | ❌ No (CLI/Testing) |
412
+
413
+ ---
414
+
415
+ ## License
416
+
417
+ MIT
@@ -0,0 +1,235 @@
1
+ const has = require('../index.js');
2
+
3
+ console.log("=".repeat(60));
4
+ console.log("=== nestify() and flatify() Detailed Tests ===");
5
+ console.log("=".repeat(60));
6
+
7
+ // Helper function to display prototype chain
8
+ function showProtoChain(obj, name = "obj") {
9
+ console.log(`\nPrototype chain for ${name}:`);
10
+ let current = obj;
11
+ let depth = 0;
12
+
13
+ while (current != null && depth < 10) {
14
+ const ownKeys = Object.keys(current);
15
+ if (ownKeys.length > 0 || depth === 0) {
16
+ const props = ownKeys.map(k => `${k}: ${current[k]}`).join(', ');
17
+ console.log(` Level ${depth}: {${props || 'empty'}}`);
18
+ }
19
+ current = Object.getPrototypeOf(current);
20
+ depth++;
21
+ if (current === Object.prototype) {
22
+ console.log(` Level ${depth}: Object.prototype`);
23
+ break;
24
+ }
25
+ }
26
+ }
27
+
28
+ // Test 1: Empty object
29
+ console.log("\n" + "=".repeat(60));
30
+ console.log("Test 1: Empty object");
31
+ console.log("=".repeat(60));
32
+ const empty = {};
33
+ const nestedEmpty = has.nestify(empty);
34
+ const flatEmpty = has.flatify(nestedEmpty);
35
+ console.log("Original:", empty);
36
+ console.log("Nestified:", nestedEmpty);
37
+ console.log("Flatified:", flatEmpty);
38
+ console.log("Match:", JSON.stringify(flatEmpty) === JSON.stringify(empty));
39
+
40
+ // Test 2: Single property
41
+ console.log("\n" + "=".repeat(60));
42
+ console.log("Test 2: Single property");
43
+ console.log("=".repeat(60));
44
+ const single = { a: 100 };
45
+ const nestedSingle = has.nestify(single);
46
+ const flatSingle = has.flatify(nestedSingle);
47
+ console.log("Original:", single);
48
+ console.log("Nestified:", nestedSingle);
49
+ console.log("Flatified:", flatSingle);
50
+ console.log("Own keys in nested:", Object.keys(nestedSingle));
51
+ console.log("Match:", flatSingle.a === 100);
52
+
53
+ // Test 3: Two properties
54
+ console.log("\n" + "=".repeat(60));
55
+ console.log("Test 3: Two properties {a:100, b:200}");
56
+ console.log("=".repeat(60));
57
+ const two = { a: 100, b: 200 };
58
+ const nestedTwo = has.nestify(two);
59
+ const flatTwo = has.flatify(nestedTwo);
60
+ console.log("Original:", two);
61
+ showProtoChain(nestedTwo, "nestedTwo");
62
+ console.log("Flatified:", flatTwo);
63
+ console.log("Values accessible:", nestedTwo.a, nestedTwo.b);
64
+ console.log("b is own?", Object.hasOwn(nestedTwo, 'b'));
65
+ console.log("a is own?", Object.hasOwn(nestedTwo, 'a'));
66
+ console.log("Match:", flatTwo.a === 100 && flatTwo.b === 200);
67
+
68
+ // Test 4: Three properties
69
+ console.log("\n" + "=".repeat(60));
70
+ console.log("Test 4: Three properties {a:1, b:2, c:3}");
71
+ console.log("=".repeat(60));
72
+ const three = { a: 1, b: 2, c: 3 };
73
+ const nestedThree = has.nestify(three);
74
+ const flatThree = has.flatify(nestedThree);
75
+ console.log("Original:", three);
76
+ showProtoChain(nestedThree, "nestedThree");
77
+ console.log("Flatified:", flatThree);
78
+ console.log("All values accessible:", nestedThree.a, nestedThree.b, nestedThree.c);
79
+ console.log("Match:", flatThree.a === 1 && flatThree.b === 2 && flatThree.c === 3);
80
+
81
+ // Test 5: Five properties
82
+ console.log("\n" + "=".repeat(60));
83
+ console.log("Test 5: Five properties");
84
+ console.log("=".repeat(60));
85
+ const five = { a: 1, b: 2, c: 3, d: 4, e: 5 };
86
+ const nestedFive = has.nestify(five);
87
+ const flatFive = has.flatify(nestedFive);
88
+ console.log("Original:", five);
89
+ showProtoChain(nestedFive, "nestedFive");
90
+ console.log("Flatified:", flatFive);
91
+ console.log("Match:", JSON.stringify(flatFive) === JSON.stringify({a:1, b:2, c:3, d:4, e:5}));
92
+
93
+ // Test 6: Different value types
94
+ console.log("\n" + "=".repeat(60));
95
+ console.log("Test 6: Different value types");
96
+ console.log("=".repeat(60));
97
+ const mixed = {
98
+ num: 42,
99
+ str: "hello",
100
+ bool: true,
101
+ nil: null,
102
+ arr: [1, 2, 3],
103
+ obj: { nested: "value" }
104
+ };
105
+ const nestedMixed = has.nestify(mixed);
106
+ const flatMixed = has.flatify(nestedMixed);
107
+ console.log("Original:", mixed);
108
+ console.log("Flatified:", flatMixed);
109
+ console.log("num match:", flatMixed.num === 42);
110
+ console.log("str match:", flatMixed.str === "hello");
111
+ console.log("bool match:", flatMixed.bool === true);
112
+ console.log("nil match:", flatMixed.nil === null);
113
+ console.log("arr match:", JSON.stringify(flatMixed.arr) === JSON.stringify([1,2,3]));
114
+ console.log("obj match:", JSON.stringify(flatMixed.obj) === JSON.stringify({nested:"value"}));
115
+
116
+ // Test 7: Property depth testing with exact()
117
+ console.log("\n" + "=".repeat(60));
118
+ console.log("Test 7: Property depth testing");
119
+ console.log("=".repeat(60));
120
+ const depth = { first: 1, second: 2, third: 3, fourth: 4 };
121
+ const nestedDepth = has.nestify(depth);
122
+ console.log("Original:", depth);
123
+ showProtoChain(nestedDepth, "nestedDepth");
124
+
125
+ console.log("\nDepth checks with exact():");
126
+ console.log("fourth at depth 0:", has.exact(nestedDepth, 'fourth', 0));
127
+ console.log("fourth at depth 1:", has.exact(nestedDepth, 'fourth', 1));
128
+ console.log("third at depth 0:", has.exact(nestedDepth, 'third', 0));
129
+ console.log("third at depth 2:", has.exact(nestedDepth, 'third', 2));
130
+ console.log("second at depth 2:", has.exact(nestedDepth, 'second', 2));
131
+ console.log("first at depth 3:", has.exact(nestedDepth, 'first', 3));
132
+
133
+ // Test 8: Round-trip preservation
134
+ console.log("\n" + "=".repeat(60));
135
+ console.log("Test 8: Round-trip preservation");
136
+ console.log("=".repeat(60));
137
+ const original = { x: 10, y: 20, z: 30, w: 40 };
138
+ const step1 = has.nestify(original);
139
+ const step2 = has.flatify(step1);
140
+ const step3 = has.nestify(step2);
141
+ const step4 = has.flatify(step3);
142
+
143
+ console.log("Original:", original);
144
+ console.log("After nest->flat:", step2);
145
+ console.log("After nest->flat->nest->flat:", step4);
146
+ console.log("Values preserved:",
147
+ step4.x === 10 && step4.y === 20 && step4.z === 30 && step4.w === 40);
148
+
149
+ // Test 9: null and undefined
150
+ console.log("\n" + "=".repeat(60));
151
+ console.log("Test 9: null and undefined handling");
152
+ console.log("=".repeat(60));
153
+ console.log("nestify(null):", has.nestify(null));
154
+ console.log("nestify(undefined):", has.nestify(undefined));
155
+ console.log("flatify(null):", has.flatify(null));
156
+ console.log("flatify(undefined):", has.flatify(undefined));
157
+
158
+ // Test 10: flatify on regular object with prototype
159
+ console.log("\n" + "=".repeat(60));
160
+ console.log("Test 10: flatify on object with custom prototype");
161
+ console.log("=".repeat(60));
162
+ const proto = { inherited: "from proto" };
163
+ const child = Object.create(proto);
164
+ child.own = "own property";
165
+ const flatChild = has.flatify(child);
166
+ console.log("Child object with prototype");
167
+ console.log("child.own:", child.own);
168
+ console.log("child.inherited:", child.inherited);
169
+ console.log("Flatified:", flatChild);
170
+ console.log("Has both properties:", flatChild.own === "own property" && flatChild.inherited === "from proto");
171
+
172
+ // Test 11: Property order preservation
173
+ console.log("\n" + "=".repeat(60));
174
+ console.log("Test 11: Property order");
175
+ console.log("=".repeat(60));
176
+ const ordered = { z: 1, y: 2, x: 3, w: 4, v: 5 };
177
+ const nestedOrdered = has.nestify(ordered);
178
+ const flatOrdered = has.flatify(nestedOrdered);
179
+ console.log("Original keys:", Object.keys(ordered));
180
+ console.log("Flatified keys:", Object.keys(flatOrdered));
181
+ console.log("Note: Order might differ but values should match");
182
+ console.log("All values present:",
183
+ flatOrdered.z === 1 && flatOrdered.y === 2 &&
184
+ flatOrdered.x === 3 && flatOrdered.w === 4 && flatOrdered.v === 5);
185
+
186
+ // Test 12: Using slow() on nestified objects
187
+ console.log("\n" + "=".repeat(60));
188
+ console.log("Test 12: Using slow() on nestified objects");
189
+ console.log("=".repeat(60));
190
+ const test = { a: 1, b: 2, c: 3 };
191
+ const nestedTest = has.nestify(test);
192
+ console.log("Original:", test);
193
+ console.log("\nUsing slow() to find properties:");
194
+ console.log("slow(nested, 'c', 0):", has.slow(nestedTest, 'c', 0)); // own
195
+ console.log("slow(nested, 'c', 1):", has.slow(nestedTest, 'c', 1)); // within 1 level
196
+ console.log("slow(nested, 'b', 1):", has.slow(nestedTest, 'b', 1)); // need to check deeper
197
+ console.log("slow(nested, 'b', 2):", has.slow(nestedTest, 'b', 2));
198
+ console.log("slow(nested, 'a', 3):", has.slow(nestedTest, 'a', 3));
199
+ console.log("slow(nested, 'a', Infinity):", has.slow(nestedTest, 'a', Infinity));
200
+
201
+ // Test 13: Performance test
202
+ console.log("\n" + "=".repeat(60));
203
+ console.log("Test 13: Performance comparison");
204
+ console.log("=".repeat(60));
205
+
206
+ const large = {};
207
+ for (let i = 0; i < 100; i++) {
208
+ large[`key${i}`] = i;
209
+ }
210
+
211
+ console.time("nestify 100 properties");
212
+ const nestedLarge = has.nestify(large);
213
+ console.timeEnd("nestify 100 properties");
214
+
215
+ console.time("flatify 100 properties");
216
+ const flatLarge = has.flatify(nestedLarge);
217
+ console.timeEnd("flatify 100 properties");
218
+
219
+ console.log("All properties preserved:", Object.keys(flatLarge).length === 100);
220
+
221
+ // Test 14: Edge case - Object with numeric keys
222
+ console.log("\n" + "=".repeat(60));
223
+ console.log("Test 14: Numeric keys");
224
+ console.log("=".repeat(60));
225
+ const numeric = { 0: "zero", 1: "one", 2: "two" };
226
+ const nestedNumeric = has.nestify(numeric);
227
+ const flatNumeric = has.flatify(nestedNumeric);
228
+ console.log("Original:", numeric);
229
+ console.log("Flatified:", flatNumeric);
230
+ console.log("Match:", flatNumeric[0] === "zero" && flatNumeric[1] === "one" && flatNumeric[2] === "two");
231
+
232
+ console.log("\n" + "=".repeat(60));
233
+ console.log("=== All tests completed! ===");
234
+ console.log("=".repeat(60));
235
+
package/TEST/tst.js ADDED
@@ -0,0 +1,136 @@
1
+ const has = require('../index.js');
2
+
3
+ console.log("=== Basic has() Tests ===\n");
4
+
5
+ // Test 1: Basic own property
6
+ const obj1 = { a: 1, b: 2 };
7
+ console.log("Test 1: Own property");
8
+ console.log("has(obj1, 'a', true):", has(obj1, 'a', true)); // true
9
+ console.log("has(obj1, 'a', false):", has(obj1, 'a', false)); // true
10
+ console.log("has(obj1, 'c', true):", has(obj1, 'c', true)); // false
11
+
12
+ // Test 2: Prototype chain
13
+ const parent = { x: 10 };
14
+ const child = Object.create(parent);
15
+ child.y = 20;
16
+
17
+ console.log("\nTest 2: Prototype chain");
18
+ console.log("has(child, 'y', true):", has(child, 'y', true)); // true (own)
19
+ console.log("has(child, 'x', true):", has(child, 'x', true)); // false (inherited)
20
+ console.log("has(child, 'x', false):", has(child, 'x', false)); // true (in chain)
21
+
22
+ // Test 3: null/undefined handling
23
+ console.log("\nTest 3: null/undefined");
24
+ console.log("has(null, 'a'):", has(null, 'a')); // false
25
+ console.log("has(undefined, 'a'):", has(undefined, 'a')); // false
26
+
27
+ console.log("\n" + "=".repeat(50));
28
+ console.log("=== Detailed has.slow() Tests ===\n");
29
+
30
+ // Setup: 3-level prototype chain
31
+ const grandparent = { level: 'grandparent', gp: 'GP' };
32
+ const parent2 = Object.create(grandparent);
33
+ parent2.level = 'parent';
34
+ parent2.p = 'P';
35
+ const child2 = Object.create(parent2);
36
+ child2.level = 'child';
37
+ child2.c = 'C';
38
+
39
+ console.log("Test Setup: 3-level chain");
40
+ console.log("child2 -> parent2 -> grandparent -> Object.prototype");
41
+ console.log("child2 has: { level: 'child', c: 'C' }");
42
+ console.log("parent2 has: { level: 'parent', p: 'P' }");
43
+ console.log("grandparent has: { level: 'grandparent', gp: 'GP' }");
44
+
45
+ // Test 4: dig = 0 (own property only)
46
+ console.log("\nTest 4: dig = 0 (own property only)");
47
+ console.log("slow(child2, 'c', 0):", has.slow(child2, 'c', 0)); // true
48
+ console.log("slow(child2, 'level', 0):", has.slow(child2, 'level', 0)); // true
49
+ console.log("slow(child2, 'p', 0):", has.slow(child2, 'p', 0)); // false
50
+ console.log("slow(child2, 'gp', 0):", has.slow(child2, 'gp', 0)); // false
51
+ console.log("slow(child2, 'toString', 0):", has.slow(child2, 'toString', 0)); // false
52
+
53
+ // Test 5: dig = 1 (search 1 level up)
54
+ console.log("\nTest 5: dig = 1 (search 1 level up)");
55
+ console.log("slow(child2, 'c', 1):", has.slow(child2, 'c', 1)); // true (own)
56
+ console.log("slow(child2, 'p', 1):", has.slow(child2, 'p', 1)); // true (parent)
57
+ console.log("slow(child2, 'gp', 1):", has.slow(child2, 'gp', 1)); // false (grandparent)
58
+ console.log("slow(child2, 'level', 1):", has.slow(child2, 'level', 1)); // true (own)
59
+
60
+ // Test 6: dig = 2 (search 2 levels up)
61
+ console.log("\nTest 6: dig = 2 (search 2 levels up)");
62
+ console.log("slow(child2, 'c', 2):", has.slow(child2, 'c', 2)); // true
63
+ console.log("slow(child2, 'p', 2):", has.slow(child2, 'p', 2)); // true
64
+ console.log("slow(child2, 'gp', 2):", has.slow(child2, 'gp', 2)); // true (grandparent)
65
+ console.log("slow(child2, 'toString', 2):", has.slow(child2, 'toString', 2)); // false
66
+
67
+ // Test 7: dig = 3 (search 3 levels up)
68
+ console.log("\nTest 7: dig = 3 (search 3 levels up)");
69
+ console.log("slow(child2, 'gp', 3):", has.slow(child2, 'gp', 3)); // true
70
+ console.log("slow(child2, 'toString', 3):", has.slow(child2, 'toString', 3)); // true (Object.prototype)
71
+
72
+ // Test 8: dig = Infinity (entire chain)
73
+ console.log("\nTest 8: dig = Infinity (entire chain)");
74
+ console.log("slow(child2, 'c', Infinity):", has.slow(child2, 'c', Infinity)); // true
75
+ console.log("slow(child2, 'p', Infinity):", has.slow(child2, 'p', Infinity)); // true
76
+ console.log("slow(child2, 'gp', Infinity):", has.slow(child2, 'gp', Infinity)); // true
77
+ console.log("slow(child2, 'toString', Infinity):", has.slow(child2, 'toString', Infinity)); // true
78
+ console.log("slow(child2, 'nonexistent', Infinity):", has.slow(child2, 'nonexistent', Infinity)); // false
79
+
80
+ // Test 9: Edge cases
81
+ console.log("\nTest 9: Edge cases");
82
+ console.log("slow(null, 'a', 0):", has.slow(null, 'a', 0)); // false
83
+ console.log("slow(undefined, 'a', 1):", has.slow(undefined, 'a', 1)); // false
84
+ console.log("slow({}, 'toString', 0):", has.slow({}, 'toString', 0)); // false (inherited)
85
+ console.log("slow({}, 'toString', 1):", has.slow({}, 'toString', 1)); // true (Object.prototype)
86
+
87
+ // Test 10: Array and built-in objects
88
+ console.log("\nTest 10: Arrays");
89
+ const arr = [1, 2, 3];
90
+ arr.custom = 'value';
91
+ console.log("slow(arr, 'custom', 0):", has.slow(arr, 'custom', 0)); // true
92
+ console.log("slow(arr, '0', 0):", has.slow(arr, '0', 0)); // true (index)
93
+ console.log("slow(arr, 'length', 0):", has.slow(arr, 'length', 0)); // true
94
+ console.log("slow(arr, 'push', 0):", has.slow(arr, 'push', 0)); // false
95
+ console.log("slow(arr, 'push', 1):", has.slow(arr, 'push', 1)); // true (Array.prototype)
96
+
97
+ // Test 11: Deep chain
98
+ console.log("\nTest 11: Deep prototype chain");
99
+ let deep = { level0: true };
100
+ for (let i = 1; i <= 5; i++) {
101
+ const newObj = Object.create(deep);
102
+ newObj[`level${i}`] = true;
103
+ deep = newObj;
104
+ }
105
+ // deep now has 5 levels
106
+ console.log("slow(deep, 'level0', 3):", has.slow(deep, 'level0', 3)); // false (need 5)
107
+ console.log("slow(deep, 'level0', 5):", has.slow(deep, 'level0', 5)); // true
108
+ console.log("slow(deep, 'level2', 3):", has.slow(deep, 'level2', 3)); // true
109
+ console.log("slow(deep, 'level5', 0):", has.slow(deep, 'level5', 0)); // true
110
+
111
+ // Test 12: Performance comparison
112
+ console.log("\nTest 12: Performance comparison");
113
+ const testObj = Object.create(Object.create({ deepProp: 1 }));
114
+ testObj.shallowProp = 2;
115
+
116
+ console.time("has() with only_own=false");
117
+ for (let i = 0; i < 100000; i++) {
118
+ has(testObj, 'deepProp', false);
119
+ }
120
+ console.timeEnd("has() with only_own=false");
121
+
122
+ console.time("slow() with dig=Infinity");
123
+ for (let i = 0; i < 100000; i++) {
124
+ has.slow(testObj, 'deepProp', Infinity);
125
+ }
126
+ console.timeEnd("slow() with dig=Infinity");
127
+
128
+ console.time("slow() with dig=2");
129
+ for (let i = 0; i < 100000; i++) {
130
+ has.slow(testObj, 'deepProp', 2);
131
+ }
132
+ console.timeEnd("slow() with dig=2");
133
+
134
+ console.log("\n" + "=".repeat(50));
135
+ console.log("All tests completed!");
136
+
package/index.js ADDED
@@ -0,0 +1,340 @@
1
+ const slow = (o, key, dig = Infinity) => {
2
+ if (o == null) {
3
+ return false;
4
+ }
5
+ if (dig === 0) {
6
+ // 只检查自身属性
7
+ return Object.hasOwn(o, key);
8
+ } else if (dig === Infinity) {
9
+ // 检查整个原型链
10
+ return (key in o);
11
+ } else {
12
+ // 沿着原型链向上查找指定层数
13
+ let current = o;
14
+ let depth = 0;
15
+
16
+ while (current != null && depth <= dig) {
17
+ if (Object.hasOwn(current, key)) {
18
+ return true;
19
+ }
20
+ current = Object.getPrototypeOf(current);
21
+ depth++;
22
+ }
23
+
24
+ return false;
25
+ }
26
+ };
27
+
28
+ const exact = (o, key, depth = Infinity) => {
29
+ if (o == null) {
30
+ return false;
31
+ }
32
+ if (depth === 0) {
33
+ // 只检查自身属性
34
+ return Object.hasOwn(o, key);
35
+ } else if (depth === Infinity) {
36
+ // 检查整个原型链
37
+ return (key in o);
38
+ } else {
39
+ // 沿着原型链向上查找指定层数
40
+ // 必须恰好在第 depth 层有此属性
41
+ let current = o;
42
+
43
+ // 移动到指定深度
44
+ for (let i = 0; i < depth; i++) {
45
+ current = Object.getPrototypeOf(current);
46
+ if (current == null) {
47
+ return false; // 原型链不够深
48
+ }
49
+ }
50
+
51
+ // 检查该层是否有此属性(自身属性)
52
+ return Object.hasOwn(current, key);
53
+ }
54
+ };
55
+
56
+ const get_proto_chain = (o) => {
57
+ if (o == null) {
58
+ return [];
59
+ }
60
+
61
+ const chain = [];
62
+ let current = o;
63
+
64
+ while (current != null) {
65
+ chain.push(current);
66
+ current = Object.getPrototypeOf(current);
67
+
68
+ // 停在 Object.prototype,不包括 null
69
+ if (current === Object.prototype) {
70
+ chain.push(current);
71
+ break;
72
+ }
73
+ }
74
+
75
+ return chain;
76
+ };
77
+
78
+ const get_prop_descs = (o, split_layer = false) => {
79
+ if (o == null) {
80
+ return split_layer ? [] : {};
81
+ }
82
+
83
+ if (split_layer) {
84
+ // 按层分割,每层返回 {object, descriptors}
85
+ const descs = [];
86
+ let current = o;
87
+
88
+ while (current != null) {
89
+ const ownDescs = Object.getOwnPropertyDescriptors(current);
90
+ descs.push({
91
+ object: current,
92
+ descriptors: ownDescs
93
+ });
94
+
95
+ current = Object.getPrototypeOf(current);
96
+
97
+ if (current === Object.prototype) {
98
+ descs.push({
99
+ object: current,
100
+ descriptors: Object.getOwnPropertyDescriptors(current)
101
+ });
102
+ break;
103
+ }
104
+ }
105
+
106
+ return descs;
107
+ } else {
108
+ // 不分层,直接合并所有描述符到一个对象
109
+ const allDescs = {};
110
+ let current = o;
111
+
112
+ while (current != null) {
113
+ const ownDescs = Object.getOwnPropertyDescriptors(current);
114
+
115
+ // 只添加还未出现的属性(保留最近层的描述符)
116
+ for (const key in ownDescs) {
117
+ if (!(key in allDescs)) {
118
+ allDescs[key] = ownDescs[key];
119
+ }
120
+ }
121
+
122
+ current = Object.getPrototypeOf(current);
123
+
124
+ if (current === Object.prototype) {
125
+ break;
126
+ }
127
+ }
128
+
129
+ return allDescs;
130
+ }
131
+ };
132
+
133
+ // 辅助函数:生成属性特征字符串
134
+ const get_prop_signature = (key, desc) => {
135
+ const isSymbol = typeof key === 'symbol';
136
+ const keyType = isSymbol ? 'y' : 'k';
137
+
138
+ let propType;
139
+ if (desc.get || desc.set) {
140
+ // accessor property
141
+ const g = desc.get ? 'g' : '_';
142
+ const s = desc.set ? 's' : '_';
143
+ propType = `${g}${s}_`;
144
+ } else {
145
+ // data property
146
+ propType = '__v';
147
+ }
148
+
149
+ const w = desc.writable ? 'w' : '_';
150
+ const e = desc.enumerable ? 'e' : '_';
151
+ const c = desc.configurable ? 'c' : '_';
152
+
153
+ return `${keyType}-${propType}-${w}${e}${c}`;
154
+ };
155
+
156
+
157
+
158
+ const get_all_keys = (
159
+ o,
160
+ split_layer = false,
161
+ enable_grouping = false
162
+ ) => {
163
+ if (o == null) {
164
+ return split_layer ? [] : (enable_grouping ? {} : []);
165
+ }
166
+
167
+
168
+
169
+ if (split_layer) {
170
+ // 按层分割
171
+ const layers = [];
172
+ let current = o;
173
+
174
+ while (current != null) {
175
+ const ownKeys = Object.getOwnPropertyNames(current);
176
+ const ownSymbols = Object.getOwnPropertySymbols(current);
177
+ const allKeys = [...ownKeys, ...ownSymbols];
178
+
179
+ if (enable_grouping) {
180
+ const grouped = {};
181
+ const descs = Object.getOwnPropertyDescriptors(current);
182
+
183
+ for (const key of allKeys) {
184
+ const desc = descs[key];
185
+ const sig = get_prop_signature(key, desc);
186
+
187
+ if (!grouped[sig]) {
188
+ grouped[sig] = [];
189
+ }
190
+ grouped[sig].push(key);
191
+ }
192
+
193
+ layers.push(grouped);
194
+ } else {
195
+ layers.push(allKeys);
196
+ }
197
+
198
+ current = Object.getPrototypeOf(current);
199
+
200
+ if (current === Object.prototype) {
201
+ break;
202
+ }
203
+ }
204
+
205
+ return layers;
206
+ } else {
207
+ // 不分层,合并所有键
208
+ const allKeys = new Set();
209
+ let current = o;
210
+
211
+ while (current != null) {
212
+ const ownKeys = Object.getOwnPropertyNames(current);
213
+ const ownSymbols = Object.getOwnPropertySymbols(current);
214
+
215
+ for (const key of [...ownKeys, ...ownSymbols]) {
216
+ allKeys.add(key);
217
+ }
218
+
219
+ current = Object.getPrototypeOf(current);
220
+
221
+ if (current === Object.prototype) {
222
+ break;
223
+ }
224
+ }
225
+
226
+ const keysArray = Array.from(allKeys);
227
+
228
+ if (enable_grouping) {
229
+ const grouped = {};
230
+
231
+ for (const key of keysArray) {
232
+ // Find descriptor in prototype chain
233
+ let desc = null;
234
+ let curr = o;
235
+
236
+ while (curr != null) {
237
+ desc = Object.getOwnPropertyDescriptor(curr, key);
238
+ if (desc) break;
239
+ curr = Object.getPrototypeOf(curr);
240
+ if (curr === Object.prototype) break;
241
+ }
242
+
243
+ if (desc) {
244
+ const sig = get_prop_signature(key, desc);
245
+
246
+ if (!grouped[sig]) {
247
+ grouped[sig] = [];
248
+ }
249
+ grouped[sig].push(key);
250
+ }
251
+ }
252
+
253
+ return grouped;
254
+ } else {
255
+ return keysArray;
256
+ }
257
+ }
258
+ };
259
+
260
+
261
+ // nestify AND flatify is for creat test-samples
262
+
263
+ const nestify = (o) => {
264
+ if (o == null) {
265
+ return o;
266
+ }
267
+
268
+ const keys = Object.keys(o);
269
+
270
+ if (keys.length === 0) {
271
+ return {};
272
+ }
273
+
274
+ if (keys.length === 1) {
275
+ return { [keys[0]]: o[keys[0]] };
276
+ }
277
+
278
+ // 例如 o = {a:100, b:200}
279
+ // 第一个键放在最深的原型
280
+ let proto = { [keys[0]]: o[keys[0]] };
281
+
282
+ // 中间的键,每个键一层
283
+ for (let i = 1; i < keys.length - 1; i++) {
284
+ const layer = { [keys[i]]: o[keys[i]] };
285
+ proto = Object.create(proto);
286
+ Object.assign(proto, layer);
287
+ }
288
+
289
+ // 创建一个空的中间层 pr
290
+ const pr = Object.create(proto);
291
+
292
+ // 创建最终对象,最后一个键放在自身
293
+ const newO = Object.create(pr);
294
+ newO[keys[keys.length - 1]] = o[keys[keys.length - 1]];
295
+
296
+ return newO;
297
+ };
298
+
299
+ const flatify = (o) => {
300
+ if (o == null) {
301
+ return o;
302
+ }
303
+
304
+ const result = {};
305
+ let current = o;
306
+
307
+ // 遍历整个原型链
308
+ while (current != null) {
309
+ // 获取当前层的自身属性
310
+ const ownKeys = Object.keys(current);
311
+ for (const key of ownKeys) {
312
+ // 只添加还未出现的属性(保留最近层的值)
313
+ if (!(key in result)) {
314
+ result[key] = current[key];
315
+ }
316
+ }
317
+ current = Object.getPrototypeOf(current);
318
+
319
+ // 避免遍历到 Object.prototype
320
+ if (current === Object.prototype) {
321
+ break;
322
+ }
323
+ }
324
+
325
+ return result;
326
+ };
327
+
328
+ module.exports = (o, key, only_own = false) => o != null && (only_own ? Object.hasOwn(o, key) : key in o);
329
+ ////除主函数外,其他函数只适合用在cli 和测试框架里,例如inspect 某个对象
330
+
331
+ module.exports.slow = slow;
332
+ module.exports.exact = exact;
333
+ module.exports.nestify = nestify;
334
+ module.exports.flatify = flatify;
335
+ module.exports.get_proto_chain = get_proto_chain;
336
+ module.exports.get_prop_descs = get_prop_descs;
337
+ module.exports.get_prop_signature = get_prop_signature;
338
+ module.exports.get_all_keys = get_all_keys;
339
+
340
+
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "nv-facutil-has",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1"
7
+ },
8
+ "author": "",
9
+ "license": "ISC",
10
+ "description": ""
11
+ }