@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 +1 -0
- package/README.md +7 -4
- package/dist/types/message.d.ts +4 -2
- package/dist/types/string.d.ts +7 -0
- package/package.json +1 -1
- package/src/message.js +22 -19
- package/src/string.js +18 -0
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`, ...),
|
|
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}+$'); //
|
|
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
|
|
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
|
|
package/dist/types/message.d.ts
CHANGED
|
@@ -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
|
package/dist/types/string.d.ts
CHANGED
|
@@ -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
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
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
|
*/
|