@jackens/nnn 2026.2.7 → 2026.2.11

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 +22 -11
  3. package/package.json +1 -1
  4. package/readme.md +125 -18
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,21 +7,32 @@ 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 get_target = (parent, parent_key, child_key) => {
11
+ if (parent_key == null) {
12
+ return parent;
13
+ }
14
+ if (!(parent_key in parent)) {
15
+ parent[parent_key] = is_string(child_key) && ARRAY_INDEX_REGEXP.test(child_key) ? [] : {};
16
+ }
17
+ return parent[parent_key];
18
+ };
19
+ var _vivify = (parent, parent_key) => new Proxy({}, {
20
+ get(_, key) {
21
+ const target = get_target(parent, parent_key, key);
22
+ if (target != null && typeof target === "object") {
23
+ return _vivify(target, key);
15
24
  }
16
- target[key] ??= {};
17
- return _vivify(target[key], target, key);
25
+ return target?.[key];
18
26
  },
19
- set(target, key, value) {
20
- if (should_convert_target_to_array(target, key, parent)) {
21
- target = parent[parentKey] = [];
22
- }
27
+ set(_, key, value) {
28
+ const target = get_target(parent, parent_key, key);
23
29
  target[key] = value;
24
30
  return true;
31
+ },
32
+ deleteProperty(_, key) {
33
+ const target = get_target(parent, parent_key, key);
34
+ delete target[key];
35
+ return true;
25
36
  }
26
37
  });
27
38
  var vivify = (ref) => _vivify(ref);
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.11"
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,41 +1280,140 @@ 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
1276
1284
 
1277
- expect(ref).to.deep.equal({ one: { two: [, , , [, , , , 1234]] } })
1285
+ expect(ref).to.deep.equal({
1286
+ one: {
1287
+ two: [
1288
+ undefined,
1289
+ [undefined, undefined, 42]
1290
+ ]
1291
+ }
1292
+ })
1278
1293
 
1279
- vivify(ref).one.two[3][5] = 1235
1294
+ vivify(ref).one.two[1][3] = 42
1280
1295
 
1281
- expect(ref).to.deep.equal({ one: { two: [, , , [, , , , 1234, 1235]] } })
1296
+ expect(ref).to.deep.equal({
1297
+ one: {
1298
+ two: [
1299
+ undefined,
1300
+ [undefined, undefined, 42, 42]
1301
+ ]
1302
+ }
1303
+ })
1282
1304
 
1283
- vivify(ref).one.two[1] = 121
1305
+ vivify(ref).one.two[1] = 42
1284
1306
 
1285
- expect(ref).to.deep.equal({ one: { two: [, 121, , [, , , , 1234, 1235]] } })
1307
+ expect(ref).to.deep.equal({
1308
+ one: {
1309
+ two: [
1310
+ undefined,
1311
+ 42
1312
+ ]
1313
+ }
1314
+ })
1315
+
1316
+ vivify(ref).one.two[3][0] = 42
1317
+
1318
+ expect(ref).to.deep.equal({
1319
+ one: {
1320
+ two: [
1321
+ undefined,
1322
+ 42,
1323
+ undefined,
1324
+ [42]
1325
+ ]
1326
+ }
1327
+ })
1328
+
1329
+ vivify(ref).one.two[3].length = 2
1330
+
1331
+ expect(ref).to.deep.equal({
1332
+ one: {
1333
+ two: [
1334
+ undefined,
1335
+ 42,
1336
+ undefined,
1337
+ [42, undefined]
1338
+ ]
1339
+ }
1340
+ })
1286
1341
 
1287
1342
  vivify(ref).one.two = 12
1288
1343
 
1289
- expect(ref).to.deep.equal({ one: { two: 12 } })
1344
+ expect(ref).to.deep.equal({
1345
+ one: {
1346
+ two: 12
1347
+ }
1348
+ })
1290
1349
 
1291
1350
  vivify(ref).one.two = undefined
1292
1351
 
1293
- expect(ref).to.deep.equal({ one: { two: undefined } })
1352
+ expect(ref).to.deep.equal({
1353
+ one: {
1354
+ two: undefined
1355
+ }
1356
+ })
1294
1357
 
1295
1358
  delete vivify(ref).one.two
1296
1359
 
1297
- expect(ref).to.deep.equal({ one: {} })
1360
+ expect(ref).to.deep.equal({
1361
+ one: {}
1362
+ })
1298
1363
 
1299
1364
  delete vivify(ref).one.two.three
1300
1365
 
1301
- expect(ref).to.deep.equal({ one: { two: {} } })
1366
+ expect(ref).to.deep.equal({
1367
+ one: {
1368
+ two: {}
1369
+ }
1370
+ })
1302
1371
 
1303
1372
  vivify(ref).one.two.three.four
1304
1373
 
1305
- expect(ref).to.deep.equal({ one: { two: { three: { four: {} } } } })
1374
+ expect(ref).to.deep.equal({
1375
+ one: {
1376
+ two: {
1377
+ three: {}
1378
+ }
1379
+ }
1380
+ })
1306
1381
 
1307
- vivify(ref).one.two.three.four = 1234
1382
+ vivify(ref).one.two[3]
1308
1383
 
1309
- expect(ref).to.deep.equal({ one: { two: { three: { four: 1234 } } } })
1384
+ expect(ref).to.deep.equal({
1385
+ one: {
1386
+ two: {
1387
+ three: {}
1388
+ }
1389
+ }
1390
+ })
1391
+
1392
+ vivify(ref).one.two.three.four = 42
1393
+
1394
+ expect(ref).to.deep.equal({
1395
+ one: {
1396
+ two: {
1397
+ three: {
1398
+ four: 42
1399
+ }
1400
+ }
1401
+ }
1402
+ })
1403
+
1404
+ const name = vivify(ref).one.two.three.four.toString.name
1405
+
1406
+ expect(name).to.deep.equal('toString')
1407
+
1408
+ expect(ref).to.deep.equal({
1409
+ one: {
1410
+ two: {
1411
+ three: {
1412
+ four: 42
1413
+ }
1414
+ }
1415
+ }
1416
+ })
1310
1417
  ```
1311
1418
 
1312
1419
  ## License