@jackens/nnn 2026.2.7 → 2026.2.13

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 (4) hide show
  1. package/nnn.d.ts +13 -5
  2. package/nnn.js +20 -17
  3. package/package.json +1 -1
  4. package/readme.md +48 -14
package/nnn.d.ts CHANGED
@@ -440,12 +440,20 @@ export declare const uuid_v1: (date?: Date, node?: string) => string;
440
440
  /**
441
441
  * A Proxy-based helper for auto-vivification of nested object structures.
442
442
  *
443
- * Accessing any property on the returned proxy automatically creates an empty object
444
- * (or array for numeric keys) if the property does not exist,
445
- * allowing deep assignments without explicit null checks.
443
+ * Accessing, assigning, or deleting any nested property on the returned proxy automatically creates intermediate objects
444
+ * (or arrays for numeric-string keys matching `^(0|[1-9]\d*)$`) as needed, allowing deep operations without explicit null checks.
446
445
  *
447
- * When a numeric key (like `[42]`) is accessed, the parent structure is
448
- * automatically converted to an array if it isn’t one already.
446
+ * Intermediates of the last level in a get-only property chain are NOT auto-created.
447
+ * For example, `vivify(ref).one.two` will create `ref.one` as `{}`, but will NOT create `ref.one.two`.
448
+ * Only when a deeper access, assignment, or deletion occurs
449
+ * (e.g. `vivify(ref).one.two[3]` or `vivify(ref).one.two.three = 42`) will `ref.one.two` be materialized.
450
+ *
451
+ * When traversal reaches a primitive value, no auto-creation happens;
452
+ * the primitive’s own property is returned instead
453
+ * (e.g. accessing `.toString.name` on a number yields `'toString'` without modifying the underlying structure).
454
+ *
455
+ * Deletion on a non-existing intermediate path will auto-create intermediates up to the parent of the deleted key
456
+ * (e.g. `delete vivify(ref).a.b.c` will create `ref.a.b` as `{}` if it does not exist).
449
457
  *
450
458
  * @param ref
451
459
  *
package/nnn.js CHANGED
@@ -7,24 +7,27 @@ var is_string = (arg) => typeof arg === "string";
7
7
 
8
8
  // src/nnn/vivify.ts
9
9
  var ARRAY_INDEX_REGEXP = /^(0|[1-9]\d*)$/;
10
- var should_convert_target_to_array = (target, key, parent) => parent != null && typeof key === "string" && ARRAY_INDEX_REGEXP.test(key) && !is_array(target);
11
- var _vivify = (ref, parent, parentKey) => new Proxy(ref, {
12
- get(target, key) {
13
- if (should_convert_target_to_array(target, key, parent)) {
14
- target = parent[parentKey] = [];
10
+ var _is_object = (ref) => typeof ref === "object";
11
+ var _get_target = (parent, parent_key, key) => parent[parent_key] ??= is_string(key) && ARRAY_INDEX_REGEXP.test(key) ? [] : {};
12
+ var _vivify = (parent, parent_key) => {
13
+ return parent != null && _is_object(parent) ? new Proxy(parent, {
14
+ get(_, key) {
15
+ const target = _get_target(parent, parent_key, key);
16
+ const value = target[key];
17
+ return is_string(key) && _is_object(target) && (value == null || _is_object(value)) ? _vivify(target, key) : value;
18
+ },
19
+ set(_, key, value) {
20
+ const target = _get_target(parent, parent_key, key);
21
+ target[key] = value;
22
+ return true;
23
+ },
24
+ deleteProperty(_, key) {
25
+ const target = _get_target(parent, parent_key, key);
26
+ return delete target[key];
15
27
  }
16
- target[key] ??= {};
17
- return _vivify(target[key], target, key);
18
- },
19
- set(target, key, value) {
20
- if (should_convert_target_to_array(target, key, parent)) {
21
- target = parent[parentKey] = [];
22
- }
23
- target[key] = value;
24
- return true;
25
- }
26
- });
27
- var vivify = (ref) => _vivify(ref);
28
+ }) : parent?.[parent_key];
29
+ };
30
+ var vivify = (ref) => _vivify({ _: ref }, "_");
28
31
 
29
32
  // src/nnn/c.ts
30
33
  var _c = (node, prefix, result, splitter) => {
package/package.json CHANGED
@@ -43,5 +43,5 @@
43
43
  "name": "@jackens/nnn",
44
44
  "type": "module",
45
45
  "types": "nnn.d.ts",
46
- "version": "2026.2.7"
46
+ "version": "2026.2.13"
47
47
  }
package/readme.md CHANGED
@@ -1252,12 +1252,20 @@ const vivify: (ref: unknown) => any;
1252
1252
 
1253
1253
  A Proxy-based helper for auto-vivification of nested object structures.
1254
1254
 
1255
- Accessing any property on the returned proxy automatically creates an empty object
1256
- (or array for numeric keys) if the property does not exist,
1257
- allowing deep assignments without explicit null checks.
1255
+ Accessing, assigning, or deleting any nested property on the returned proxy automatically creates intermediate objects
1256
+ (or arrays for numeric-string keys matching `^(0|[1-9]\d*)$`) as needed, allowing deep operations without explicit null checks.
1258
1257
 
1259
- When a numeric key (like `[42]`) is accessed, the parent structure is
1260
- automatically converted to an array if it isn’t one already.
1258
+ Intermediates of the last level in a get-only property chain are NOT auto-created.
1259
+ For example, `vivify(ref).one.two` will create `ref.one` as `{}`, but will NOT create `ref.one.two`.
1260
+ Only when a deeper access, assignment, or deletion occurs
1261
+ (e.g. `vivify(ref).one.two[3]` or `vivify(ref).one.two.three = 42`) will `ref.one.two` be materialized.
1262
+
1263
+ When traversal reaches a primitive value, no auto-creation happens;
1264
+ the primitive’s own property is returned instead
1265
+ (e.g. accessing `.toString.name` on a number yields `'toString'` without modifying the underlying structure).
1266
+
1267
+ Deletion on a non-existing intermediate path will auto-create intermediates up to the parent of the deleted key
1268
+ (e.g. `delete vivify(ref).a.b.c` will create `ref.a.b` as `{}` if it does not exist).
1261
1269
 
1262
1270
  #### ref
1263
1271
 
@@ -1272,17 +1280,29 @@ A proxy that auto-creates nested objects/arrays on property access.
1272
1280
  ```ts
1273
1281
  const ref: any = {}
1274
1282
 
1275
- vivify(ref).one.two[3][4] = 1234
1283
+ vivify(ref).one.two[1][2] = 42
1284
+
1285
+ expect(ref).to.deep.equal({ one: { two: [undefined, [undefined, undefined, 42]] } })
1286
+
1287
+ vivify(ref).one.two[1][3] = 42
1288
+
1289
+ expect(ref).to.deep.equal({ one: { two: [undefined, [undefined, undefined, 42, 42]] } })
1290
+
1291
+ vivify(ref).one.two[1] = 42
1292
+
1293
+ expect(ref).to.deep.equal({ one: { two: [undefined, 42] } })
1294
+
1295
+ vivify(ref).one.two[3][0] = 42
1276
1296
 
1277
- expect(ref).to.deep.equal({ one: { two: [, , , [, , , , 1234]] } })
1297
+ expect(ref).to.deep.equal({ one: { two: [undefined, 42, undefined, [42]] } })
1278
1298
 
1279
- vivify(ref).one.two[3][5] = 1235
1299
+ vivify(ref).one.two[3].length = 2
1280
1300
 
1281
- expect(ref).to.deep.equal({ one: { two: [, , , [, , , , 1234, 1235]] } })
1301
+ expect(ref).to.deep.equal({ one: { two: [undefined, 42, undefined, [42, undefined]] } })
1282
1302
 
1283
- vivify(ref).one.two[1] = 121
1303
+ expect(vivify(ref).one.two.length).to.deep.equal(4)
1284
1304
 
1285
- expect(ref).to.deep.equal({ one: { two: [, 121, , [, , , , 1234, 1235]] } })
1305
+ expect(ref).to.deep.equal({ one: { two: [undefined, 42, undefined, [42, undefined]] } })
1286
1306
 
1287
1307
  vivify(ref).one.two = 12
1288
1308
 
@@ -1296,17 +1316,31 @@ delete vivify(ref).one.two
1296
1316
 
1297
1317
  expect(ref).to.deep.equal({ one: {} })
1298
1318
 
1319
+ expect(vivify(ref).one.toString instanceof Function).to.be.true
1320
+
1321
+ expect(vivify(ref).one.toString.name).to.equal('toString')
1322
+
1323
+ expect(ref).to.deep.equal({ one: {} })
1324
+
1299
1325
  delete vivify(ref).one.two.three
1300
1326
 
1301
1327
  expect(ref).to.deep.equal({ one: { two: {} } })
1302
1328
 
1303
1329
  vivify(ref).one.two.three.four
1304
1330
 
1305
- expect(ref).to.deep.equal({ one: { two: { three: { four: {} } } } })
1331
+ expect(ref).to.deep.equal({ one: { two: { three: {} } } })
1332
+
1333
+ vivify(ref).one.two[3]
1334
+
1335
+ expect(ref).to.deep.equal({ one: { two: { three: {} } } })
1336
+
1337
+ vivify(ref).one.two.three.four = 42
1338
+
1339
+ expect(ref).to.deep.equal({ one: { two: { three: { four: 42 } } } })
1306
1340
 
1307
- vivify(ref).one.two.three.four = 1234
1341
+ expect(vivify(ref).one.two.three.four.toString.name).to.deep.equal('toString')
1308
1342
 
1309
- expect(ref).to.deep.equal({ one: { two: { three: { four: 1234 } } } })
1343
+ expect(ref).to.deep.equal({ one: { two: { three: { four: 42 } } } })
1310
1344
  ```
1311
1345
 
1312
1346
  ## License