@jackens/nnn 2026.2.6 → 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 +20 -12
  2. package/nnn.js +24 -15
  3. package/package.json +1 -1
  4. package/readme.md +136 -25
package/nnn.d.ts CHANGED
@@ -312,16 +312,16 @@ export declare const js_on_parse: (handlers: Record<PropertyKey, Function>, text
312
312
  * Different languages have different plural rules. The `Intl.PluralRules` API provides
313
313
  * locale-aware plural category selection. Possible categories are:
314
314
  *
315
- * - `zero` - for zero items (used in some languages like Arabic, Latvian)
316
- * - `one` - for singular (e.g., 1 item)
317
- * - `two` - for dual (used in some languages like Arabic, Hebrew)
318
- * - `few` - for small plurals (e.g., 2-4 in Polish)
319
- * - `many` - for larger plurals (e.g., 5-21 in Polish)
320
- * - `other` - fallback category (used by all languages)
315
+ * - `zero`: for zero items (used in some languages like Arabic, Latvian)
316
+ * - `one`: for singular (e.g., 1 item)
317
+ * - `two`: for dual (used in some languages like Arabic, Hebrew)
318
+ * - `few`: for small plurals (e.g., 2-4 in Polish)
319
+ * - `many`: for larger plurals (e.g., 5-21 in Polish)
320
+ * - `other`: fallback category (used by all languages)
321
321
  *
322
322
  * @param locale
323
323
  *
324
- * A BCP 47 language tag (e.g., "pl", "en").
324
+ * A BCP 47 language tag (e.g., `pl`, `en`).
325
325
  *
326
326
  * @param forms
327
327
  *
@@ -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);
@@ -224,10 +235,8 @@ var js_on_parse = (handlers, text) => JSON.parse(text, (key, value) => {
224
235
  return value;
225
236
  });
226
237
  // src/nnn/new_noun_form.ts
227
- var new_noun_form = (locale, forms) => {
228
- const plural_rules = new Intl.PluralRules(locale);
229
- return (value) => forms[plural_rules.select(value)] ?? forms.other ?? "";
230
- };
238
+ var PLURAL_RULES = {};
239
+ var new_noun_form = (locale, forms) => (value) => forms[(PLURAL_RULES[locale] ??= new Intl.PluralRules(locale)).select(value)] ?? forms.other ?? "";
231
240
  // src/nnn/omit_pick.ts
232
241
  var pick = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => keys.includes(key)));
233
242
  var omit = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => !keys.includes(key)));
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.6"
46
+ "version": "2026.2.11"
47
47
  }
package/readme.md CHANGED
@@ -918,16 +918,16 @@ Creates a function that returns the appropriate noun form based on a numeric val
918
918
  Different languages have different plural rules. The `Intl.PluralRules` API provides
919
919
  locale-aware plural category selection. Possible categories are:
920
920
 
921
- - `zero` - for zero items (used in some languages like Arabic, Latvian)
922
- - `one` - for singular (e.g., 1 item)
923
- - `two` - for dual (used in some languages like Arabic, Hebrew)
924
- - `few` - for small plurals (e.g., 2-4 in Polish)
925
- - `many` - for larger plurals (e.g., 5-21 in Polish)
926
- - `other` - fallback category (used by all languages)
921
+ - `zero`: for zero items (used in some languages like Arabic, Latvian)
922
+ - `one`: for singular (e.g., 1 item)
923
+ - `two`: for dual (used in some languages like Arabic, Hebrew)
924
+ - `few`: for small plurals (e.g., 2-4 in Polish)
925
+ - `many`: for larger plurals (e.g., 5-21 in Polish)
926
+ - `other`: fallback category (used by all languages)
927
927
 
928
928
  #### locale
929
929
 
930
- A BCP 47 language tag (e.g., "pl", "en").
930
+ A BCP 47 language tag (e.g., `pl`, `en`).
931
931
 
932
932
  #### forms
933
933
 
@@ -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,37 +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
1284
+
1285
+ expect(ref).to.deep.equal({
1286
+ one: {
1287
+ two: [
1288
+ undefined,
1289
+ [undefined, undefined, 42]
1290
+ ]
1291
+ }
1292
+ })
1293
+
1294
+ vivify(ref).one.two[1][3] = 42
1276
1295
 
1277
- expect(ref).to.deep.equal({ one: { two: [, , , [, , , , 1234]] } })
1296
+ expect(ref).to.deep.equal({
1297
+ one: {
1298
+ two: [
1299
+ undefined,
1300
+ [undefined, undefined, 42, 42]
1301
+ ]
1302
+ }
1303
+ })
1304
+
1305
+ vivify(ref).one.two[1] = 42
1306
+
1307
+ expect(ref).to.deep.equal({
1308
+ one: {
1309
+ two: [
1310
+ undefined,
1311
+ 42
1312
+ ]
1313
+ }
1314
+ })
1278
1315
 
1279
- vivify(ref).one.two[3][5] = 12345
1316
+ vivify(ref).one.two[3][0] = 42
1280
1317
 
1281
- expect(ref).to.deep.equal({ one: { two: [, , , [, , , , 1234, 12345]] } })
1318
+ expect(ref).to.deep.equal({
1319
+ one: {
1320
+ two: [
1321
+ undefined,
1322
+ 42,
1323
+ undefined,
1324
+ [42]
1325
+ ]
1326
+ }
1327
+ })
1282
1328
 
1283
- vivify(ref).one.two[1] = 'one'
1329
+ vivify(ref).one.two[3].length = 2
1284
1330
 
1285
- expect(ref).to.deep.equal({ one: { two: [, 'one', , [, , , , 1234, 12345]] } })
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
- vivify(ref).one.two = 'tree'
1342
+ vivify(ref).one.two = 12
1288
1343
 
1289
- expect(ref).to.deep.equal({ one: { two: 'tree' } })
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
+ })
1363
+
1364
+ delete vivify(ref).one.two.three
1365
+
1366
+ expect(ref).to.deep.equal({
1367
+ one: {
1368
+ two: {}
1369
+ }
1370
+ })
1298
1371
 
1299
1372
  vivify(ref).one.two.three.four
1300
1373
 
1301
- expect(ref).to.deep.equal({ one: { two: { three: { four: {} } } } })
1374
+ expect(ref).to.deep.equal({
1375
+ one: {
1376
+ two: {
1377
+ three: {}
1378
+ }
1379
+ }
1380
+ })
1381
+
1382
+ vivify(ref).one.two[3]
1383
+
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
+ })
1302
1403
 
1303
- vivify(ref).one.two.three.four = 1234
1404
+ const name = vivify(ref).one.two.three.four.toString.name
1304
1405
 
1305
- expect(ref).to.deep.equal({ one: { two: { three: { four: 1234 } } } })
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
+ })
1306
1417
  ```
1307
1418
 
1308
1419
  ## License