@jarenjs/core 0.67.0 → 0.72.2

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/ARCHITECTURE.md CHANGED
@@ -445,6 +445,7 @@ flowchart TB
445
445
  RegExpUtils --> IsRegExp["isRegExpType()"]
446
446
  RegExpUtils --> IsStringRegExp["isStringRegExp()<br/>(tests if valid pattern)"]
447
447
  RegExpUtils --> CreateRegExp["createRegExp()<br/>(handles /pattern/flags syntax)"]
448
+ RegExpUtils --> TestRegExp["createRegExpTester()<br/>(repeatable, isolated global/sticky state)"]
448
449
 
449
450
  Unicode["Unicode Support"]
450
451
  Unicode --> Ascii["isAsciiString()"]
package/README.md CHANGED
@@ -11,7 +11,7 @@ None of it depends on JSON Schema: every module can be used standalone in any Ja
11
11
  | `@jarenjs/core` | type guards and getters (`isStringType`, `isObjectClass`, `getIntegerType`, ...) |
12
12
  | `@jarenjs/core/array` | array helpers (`isUniqueArray`, `getUniqueArray`, `includesAll`, ...) |
13
13
  | `@jarenjs/core/object` | deep equality (`equalsDeep`, JSON-only `equalsJson`), the `isJsonObject` and deep `isJsonValue` predicates, `__proto__`-safe `setObjectMember`, `deepFreeze`, map/set merging |
14
- | `@jarenjs/core/string` | Unicode string helpers (`countCodePoints`, `compareCodePoints`, ...), cached regex compilation, the suite's one content hash (`fnv1a` and the `hashContent` fingerprint over it) and `kebabCase` |
14
+ | `@jarenjs/core/string` | Unicode string helpers (`countCodePoints`, `compareCodePoints`, ...), regex compilation and repeatable `createRegExpTester` predicates, the suite's one content hash (`fnv1a` and the `hashContent` fingerprint over it) and `kebabCase` |
15
15
  | `@jarenjs/core/cache` | the bounded LRU (`createBoundedCache`), the reference-keyed `createWeakCache`, and `createSemanticCache` — keyed by what a value IS, for caches whose entries decide a result |
16
16
  | `@jarenjs/core/random` | the suite's one seeded generator (`mulberry32`, pinned sequence, ToUint32 seed) and the draws built on it: `randomInt` over a half-open range, in-place Fisher–Yates `shuffle`, and `drawDistinct` — `k` distinct indices from one stream |
17
17
  | `@jarenjs/core/runtime` | the runtime record — `createRuntime({ now, uuid, random, zoneProvider })`, frozen, defaulting member for member to the platform's own (`Date.now`, `crypto.randomUUID`, `Math.random`, no zone provider) — that the store (its query deadlines included), the jobs engine, the migration runner, every contract binding and the contract memory ledger take as `runtime`, so a deterministic run is configured once; a subsystem's own explicit option wins over the record, and the record reaches hosts, never query compilation |
@@ -41,14 +41,16 @@ Deep imports work too (`@jarenjs/core/text/email`, `@jarenjs/core/math/vec2f64`,
41
41
  `@jarenjs/core/string` counts string length the way JSON Schema expects — by grapheme cluster, not UTF-16 code units — without paying for `Intl.Segmenter` unless the string actually needs it:
42
42
 
43
43
  ```javascript
44
- import { getStringLength, isAsciiString, createRegExp } from '@jarenjs/core/string';
44
+ import { getStringLength, isAsciiString, createRegExp, createRegExpTester } from '@jarenjs/core/string';
45
45
 
46
46
  getStringLength('hello', true); // 5 (ASCII fast path: str.length)
47
47
  getStringLength('héllo', true); // 5 (surrogate-aware code point count)
48
48
  getStringLength('👨‍👩‍👧‍👦', true); // 1 (grapheme segmentation, only when clusters can form)
49
49
  getStringLength('👨‍👩‍👧‍👦'); // 11 (default: plain UTF-16 length)
50
50
 
51
- createRegExp('^\\p{L}+$'); // cached, unicode-flagged RegExp
51
+ createRegExp('^\\p{L}+$'); // unicode-flagged RegExp
52
+ const matches = createRegExpTester('/^x/gi');
53
+ matches('X'); matches('X'); // true both times; global/sticky state is isolated
52
54
  ```
53
55
 
54
56
  ## Text validation
@@ -88,7 +90,7 @@ orient2d(0, 0, 1, 0, 0, 1); // > 0 — counter-clockwise, exa
88
90
  haversineDistance(4.9041, 52.3676, 2.3522, 48.8566); // 429_862 m (Amsterdam–Paris)
89
91
  ringWinding([[0,0],[1,0],[1,1],[0,1],[0,0]]); // 1 — an RFC 7946 exterior ring
90
92
  bboxOf({ type: 'Polygon', coordinates: [[[4,52],[5,52],[5,53],[4,52]]] }); // [4, 52, 5, 53]
91
- geohashEncode(4.9041, 52.3676, 5); // 'u173z' — a string, so a prefix test is proximity
93
+ geohashEncode(4.9041, 52.3676, 5); // 'u173z' — a string identifying a cell; use distances for proximity
92
94
 
93
95
  wktToGeoJson('POINT (4.9041 52.3676)'); // { type: 'Point', coordinates: [4.9041, 52.3676] }
94
96
  geoJsonToWkt({ type: 'Point', coordinates: [4.9041, 52.3676] }); // 'POINT (4.9041 52.3676)'
@@ -179,6 +181,7 @@ The kernel is measured against one-pass loops written for one question and again
179
181
  import { compileMessageTemplate, compileMessageCatalog } from '@jarenjs/core/message';
180
182
 
181
183
  const t = compileMessageTemplate('must be {comparison} {limit}');
184
+ // t.parameters is the frozen unique list ['comparison', 'limit'] from this parser.
182
185
  t({ comparison: '>=', limit: 18 }); // 'must be >= 18'
183
186
  ```
184
187
 
@@ -38,9 +38,11 @@ export declare function formatMessageValue(value: unknown): string;
38
38
  * instead of rendering as `undefined`); `{{` escapes a literal `{`.
39
39
  *
40
40
  * @param {string} template - The template text
41
- * @returns {(params: object, error?: object) => string} The compiled render closure
41
+ * @returns {((params: object, error?: object) => string) & { readonly parameters: readonly string[] }} The compiled render closure and its unique placeholder names
42
42
  */
43
- export declare function compileMessageTemplate(template: string): (params: object, error?: object) => string;
43
+ export declare function compileMessageTemplate(template: string): ((params: object, error?: object) => string) & {
44
+ readonly parameters: readonly string[];
45
+ };
44
46
  /**
45
47
  * Compile a catalog-like object into a functions-only frozen catalog.
46
48
  * Entries may be render closures (kept as-is) or template strings
@@ -33,6 +33,13 @@ export declare function isStringRegExp(data: string | RegExp | null | undefined)
33
33
  * @returns {RegExp | undefined}
34
34
  */
35
35
  export declare function createRegExp(pattern: string | RegExp | null | undefined): RegExp | undefined;
36
+ /**
37
+ * Compile a pattern into a repeatable predicate. Global and sticky patterns
38
+ * start at index zero on every call and never change a caller's RegExp state.
39
+ * @param {string | RegExp | null | undefined} pattern - The regular expression
40
+ * @returns {((value: string) => boolean) | undefined} The tester, or undefined when no pattern was supplied
41
+ */
42
+ export declare function createRegExpTester(pattern: string | RegExp | null | undefined): ((value: string) => boolean) | undefined;
36
43
  export declare function getSegmenter(): Intl.Segmenter;
37
44
  /**
38
45
  * @param {string} str
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jarenjs/core",
3
3
  "private": false,
4
- "version": "0.67.0",
4
+ "version": "0.72.2",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "types": "./dist/types/index.d.ts",
package/src/message.js CHANGED
@@ -1,4 +1,5 @@
1
1
  //@ts-check
2
+ import { setObjectMember } from './object.js';
2
3
 
3
4
  /**
4
5
  * Human-message templating: the shared half of every message catalog in
@@ -49,7 +50,7 @@ export function formatMessageValue(value) {
49
50
  * instead of rendering as `undefined`); `{{` escapes a literal `{`.
50
51
  *
51
52
  * @param {string} template - The template text
52
- * @returns {(params: object, error?: object) => string} The compiled render closure
53
+ * @returns {((params: object, error?: object) => string) & { readonly parameters: readonly string[] }} The compiled render closure and its unique placeholder names
53
54
  */
54
55
  export function compileMessageTemplate(template) {
55
56
  /** @type {string[]} literal parts between placeholders */
@@ -79,22 +80,24 @@ export function compileMessageTemplate(template) {
79
80
  }
80
81
  parts.push(literal);
81
82
 
82
- if (names.length === 0) {
83
- const text = parts[0];
84
- return function renderLiteralTemplate() { return text; };
85
- }
86
-
87
- return function renderMessageTemplate(params) {
88
- let out = parts[0];
89
- for (let i = 0; i < names.length; ++i) {
90
- const name = names[i];
91
- out += (params != null && name in params)
92
- ? formatTemplateParam(params[name])
93
- : `{${name}}`;
94
- out += parts[i + 1];
95
- }
96
- return out;
97
- };
83
+ const render = names.length === 0
84
+ ? function renderLiteralTemplate() { return parts[0]; }
85
+ : function renderMessageTemplate(params) {
86
+ let out = parts[0];
87
+ for (let i = 0; i < names.length; ++i) {
88
+ const name = names[i];
89
+ out += (params != null && name in params)
90
+ ? formatTemplateParam(params[name])
91
+ : `{${name}}`;
92
+ out += parts[i + 1];
93
+ }
94
+ return out;
95
+ };
96
+ // Metadata comes from the parser that renders the message; consumers must
97
+ // never parse placeholders with a second, subtly different grammar.
98
+ return Object.defineProperty(render, 'parameters', {
99
+ value: Object.freeze([...new Set(names)]), enumerable: true,
100
+ });
98
101
  }
99
102
 
100
103
  /**
@@ -111,9 +114,9 @@ export function compileMessageCatalog(catalogLike) {
111
114
  const keys = Object.keys(catalogLike);
112
115
  for (let i = 0; i < keys.length; ++i) {
113
116
  const entry = catalogLike[keys[i]];
114
- compiled[keys[i]] = typeof entry === 'function'
117
+ setObjectMember(compiled, keys[i], typeof entry === 'function'
115
118
  ? entry
116
- : compileMessageTemplate(String(entry));
119
+ : compileMessageTemplate(String(entry)));
117
120
  }
118
121
  return Object.freeze(compiled);
119
122
  }
package/src/string.js CHANGED
@@ -88,6 +88,24 @@ export function createRegExp(pattern) {
88
88
  throw new Error(`Unknown Regular Expression Pattern Type: ${pattern}`);
89
89
  }
90
90
 
91
+ /**
92
+ * Compile a pattern into a repeatable predicate. Global and sticky patterns
93
+ * start at index zero on every call and never change a caller's RegExp state.
94
+ * @param {string | RegExp | null | undefined} pattern - The regular expression
95
+ * @returns {((value: string) => boolean) | undefined} The tester, or undefined when no pattern was supplied
96
+ */
97
+ export function createRegExpTester(pattern) {
98
+ const parsed = createRegExp(pattern);
99
+ if (parsed === undefined) return undefined;
100
+ if (!parsed.global && !parsed.sticky)
101
+ return (value) => parsed.test(value);
102
+ const owned = new RegExp(parsed.source, parsed.flags);
103
+ return (value) => {
104
+ owned.lastIndex = 0;
105
+ return owned.test(value);
106
+ };
107
+ }
108
+
91
109
  /**
92
110
  * @type {Intl.Segmenter | null}
93
111
  */