@gtkx/utils 0.21.0 → 1.0.0-rc.1

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 (52) hide show
  1. package/README.md +173 -0
  2. package/dist/class.d.ts +17 -11
  3. package/dist/class.d.ts.map +1 -1
  4. package/dist/class.js +27 -1
  5. package/dist/class.js.map +1 -1
  6. package/dist/collection.d.ts +17 -45
  7. package/dist/collection.d.ts.map +1 -1
  8. package/dist/collection.js +42 -76
  9. package/dist/collection.js.map +1 -1
  10. package/dist/error.d.ts +16 -8
  11. package/dist/error.d.ts.map +1 -1
  12. package/dist/error.js +35 -8
  13. package/dist/error.js.map +1 -1
  14. package/dist/graceful-shutdown.d.ts +9 -57
  15. package/dist/graceful-shutdown.d.ts.map +1 -1
  16. package/dist/graceful-shutdown.js +70 -75
  17. package/dist/graceful-shutdown.js.map +1 -1
  18. package/dist/index.d.ts +9 -6
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +9 -5
  21. package/dist/index.js.map +1 -1
  22. package/dist/log.d.ts +103 -0
  23. package/dist/log.d.ts.map +1 -0
  24. package/dist/log.js +129 -0
  25. package/dist/log.js.map +1 -0
  26. package/dist/package-version.d.ts +7 -0
  27. package/dist/package-version.d.ts.map +1 -0
  28. package/dist/package-version.js +8 -0
  29. package/dist/package-version.js.map +1 -0
  30. package/dist/reflect.d.ts +11 -0
  31. package/dist/reflect.d.ts.map +1 -0
  32. package/dist/reflect.js +14 -0
  33. package/dist/reflect.js.map +1 -0
  34. package/dist/source.d.ts +12 -25
  35. package/dist/source.d.ts.map +1 -1
  36. package/dist/source.js +20 -41
  37. package/dist/source.js.map +1 -1
  38. package/dist/string.d.ts +13 -40
  39. package/dist/string.d.ts.map +1 -1
  40. package/dist/string.js +18 -48
  41. package/dist/string.js.map +1 -1
  42. package/package.json +20 -7
  43. package/src/class.ts +30 -11
  44. package/src/collection.ts +43 -81
  45. package/src/error.ts +38 -8
  46. package/src/graceful-shutdown.ts +87 -114
  47. package/src/index.ts +18 -9
  48. package/src/log.ts +164 -0
  49. package/src/package-version.ts +9 -0
  50. package/src/reflect.ts +13 -0
  51. package/src/source.ts +24 -43
  52. package/src/string.ts +19 -47
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Calls the named method on `target` with the given arguments, returning `undefined` when the
3
+ * property is missing or not callable.
4
+ *
5
+ * @param target The object to read the method from and bind as `this`.
6
+ * @param method The method name to look up.
7
+ * @param args Arguments passed to the method.
8
+ * @returns The method's return value, or `undefined` when it is not a function.
9
+ */
10
+ export const callMethod = (target, method, args) => {
11
+ const fn = Reflect.get(target, method);
12
+ return typeof fn === "function" ? Reflect.apply(fn, target, args) : undefined;
13
+ };
14
+ //# sourceMappingURL=reflect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reflect.js","sourceRoot":"","sources":["../src/reflect.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAAc,EAAE,MAAc,EAAE,IAAe,EAAW,EAAE;IACnF,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,OAAO,OAAO,EAAE,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAClF,CAAC,CAAC","sourcesContent":["/**\n * Calls the named method on `target` with the given arguments, returning `undefined` when the\n * property is missing or not callable.\n *\n * @param target The object to read the method from and bind as `this`.\n * @param method The method name to look up.\n * @param args Arguments passed to the method.\n * @returns The method's return value, or `undefined` when it is not a function.\n */\nexport const callMethod = (target: object, method: string, args: unknown[]): unknown => {\n const fn = Reflect.get(target, method);\n return typeof fn === \"function\" ? Reflect.apply(fn, target, args) : undefined;\n};\n"]}
package/dist/source.d.ts CHANGED
@@ -1,34 +1,21 @@
1
1
  /**
2
- * Helpers for shaping values into safe JavaScript/TypeScript source fragments:
3
- * a reserved-word-safe identifier and a source-safe string literal. Both are
4
- * pure and runtime-agnostic, intended for code generators that emit TypeScript.
2
+ * Returns the name unchanged, or with a trailing underscore when it collides with a reserved word,
3
+ * so it is safe to emit as a JavaScript identifier.
4
+ *
5
+ * @param name The candidate identifier.
5
6
  */
7
+ export declare const sanitizeIdentifier: (name: string) => string;
6
8
  /**
7
- * Rewrites a candidate name into a JavaScript identifier safe to use at
8
- * variable, parameter, or property position.
9
- *
10
- * The input is expected to already use valid identifier characters (for
11
- * example the output of a case-conversion helper); the only transformation
12
- * applied is appending an underscore when `name` collides with a reserved
13
- * word or global identifier, so `toIdentifier("class")` is `"class_"` and
14
- * `toIdentifier("iconName")` is `"iconName"`.
9
+ * Converts a name to camelCase and sanitizes it into a valid JavaScript identifier.
15
10
  *
16
- * @param name - The candidate identifier.
17
- * @returns A reserved-word-safe identifier.
11
+ * @param name The name to convert.
18
12
  */
19
- export declare const toIdentifier: (name: string) => string;
13
+ export declare const toCamelIdentifier: (name: string) => string;
20
14
  /**
21
- * Quotes a string for safe embedding as a literal in generated TypeScript
22
- * source.
23
- *
24
- * Builds a double-quoted literal with `JSON.stringify`, then escapes the
25
- * characters that are valid inside a JSON string yet unsafe inside JavaScript
26
- * source: the angle brackets that could otherwise break out of an enclosing
27
- * `</script>` and the U+2028/U+2029 line separators that prematurely terminate
28
- * a string literal. The escaped form parses back to the original value.
15
+ * Encodes a string as a JavaScript string literal, additionally escaping characters that are unsafe
16
+ * to embed in generated source (angle brackets and the line and paragraph separators).
29
17
  *
30
- * @param value - The string to embed.
31
- * @returns A source-safe double-quoted string literal.
18
+ * @param value The string to encode.
32
19
  */
33
- export declare const quote: (value: string) => string;
20
+ export declare const sourceStringLiteral: (value: string) => string;
34
21
  //# sourceMappingURL=source.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqDH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAAkD,CAAC;AAmB/F;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,KAAG,MAA8E,CAAC"}
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAoDA;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,SAAU,MAAM,KAAG,MAAkD,CAAC;AAErG;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,SAAU,MAAM,KAAG,MAA+C,CAAC;AAWjG;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,UAAW,MAAM,KAAG,MAC0C,CAAC"}
package/dist/source.js CHANGED
@@ -1,9 +1,4 @@
1
- /**
2
- * Helpers for shaping values into safe JavaScript/TypeScript source fragments:
3
- * a reserved-word-safe identifier and a source-safe string literal. Both are
4
- * pure and runtime-agnostic, intended for code generators that emit TypeScript.
5
- */
6
- /** Reserved words and global identifiers a generated identifier must not collide with. */
1
+ import { toCamelCase } from "./string.js";
7
2
  const RESERVED = new Set([
8
3
  "arguments",
9
4
  "await",
@@ -54,46 +49,30 @@ const RESERVED = new Set([
54
49
  "yield",
55
50
  ]);
56
51
  /**
57
- * Rewrites a candidate name into a JavaScript identifier safe to use at
58
- * variable, parameter, or property position.
52
+ * Returns the name unchanged, or with a trailing underscore when it collides with a reserved word,
53
+ * so it is safe to emit as a JavaScript identifier.
59
54
  *
60
- * The input is expected to already use valid identifier characters (for
61
- * example the output of a case-conversion helper); the only transformation
62
- * applied is appending an underscore when `name` collides with a reserved
63
- * word or global identifier, so `toIdentifier("class")` is `"class_"` and
64
- * `toIdentifier("iconName")` is `"iconName"`.
55
+ * @param name The candidate identifier.
56
+ */
57
+ export const sanitizeIdentifier = (name) => (RESERVED.has(name) ? `${name}_` : name);
58
+ /**
59
+ * Converts a name to camelCase and sanitizes it into a valid JavaScript identifier.
65
60
  *
66
- * @param name - The candidate identifier.
67
- * @returns A reserved-word-safe identifier.
61
+ * @param name The name to convert.
68
62
  */
69
- export const toIdentifier = (name) => (RESERVED.has(name) ? `${name}_` : name);
70
- const UNSAFE_SOURCE_CHARS = /[<>\u2028\u2029]/g;
71
- const escapeSourceChar = (char) => {
72
- switch (char) {
73
- case "<":
74
- return "\\u003C";
75
- case ">":
76
- return "\\u003E";
77
- case "\u2028":
78
- return "\\u2028";
79
- case "\u2029":
80
- return "\\u2029";
81
- default:
82
- return char;
83
- }
63
+ export const toCamelIdentifier = (name) => sanitizeIdentifier(toCamelCase(name));
64
+ const SOURCE_ESCAPES = {
65
+ "<": "\\u003C",
66
+ ">": "\\u003E",
67
+ "\u2028": "\\u2028",
68
+ "\u2029": "\\u2029",
84
69
  };
70
+ const UNSAFE_SOURCE_CHARS = new RegExp(`[${Object.keys(SOURCE_ESCAPES).join("")}]`, "g");
85
71
  /**
86
- * Quotes a string for safe embedding as a literal in generated TypeScript
87
- * source.
88
- *
89
- * Builds a double-quoted literal with `JSON.stringify`, then escapes the
90
- * characters that are valid inside a JSON string yet unsafe inside JavaScript
91
- * source: the angle brackets that could otherwise break out of an enclosing
92
- * `</script>` and the U+2028/U+2029 line separators that prematurely terminate
93
- * a string literal. The escaped form parses back to the original value.
72
+ * Encodes a string as a JavaScript string literal, additionally escaping characters that are unsafe
73
+ * to embed in generated source (angle brackets and the line and paragraph separators).
94
74
  *
95
- * @param value - The string to embed.
96
- * @returns A source-safe double-quoted string literal.
75
+ * @param value The string to encode.
97
76
  */
98
- export const quote = (value) => JSON.stringify(value).replace(UNSAFE_SOURCE_CHARS, escapeSourceChar);
77
+ export const sourceStringLiteral = (value) => JSON.stringify(value).replace(UNSAFE_SOURCE_CHARS, (char) => SOURCE_ESCAPES[char] ?? char);
99
78
  //# sourceMappingURL=source.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,0FAA0F;AAC1F,MAAM,QAAQ,GAAwB,IAAI,GAAG,CAAC;IAC1C,WAAW;IACX,OAAO;IACP,OAAO;IACP,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,UAAU;IACV,UAAU;IACV,SAAS;IACT,QAAQ;IACR,IAAI;IACJ,MAAM;IACN,MAAM;IACN,MAAM;IACN,QAAQ;IACR,SAAS;IACT,OAAO;IACP,SAAS;IACT,KAAK;IACL,UAAU;IACV,IAAI;IACJ,QAAQ;IACR,IAAI;IACJ,YAAY;IACZ,WAAW;IACX,KAAK;IACL,KAAK;IACL,MAAM;IACN,SAAS;IACT,SAAS;IACT,WAAW;IACX,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,MAAM;IACN,OAAO;IACP,MAAM;IACN,KAAK;IACL,QAAQ;IACR,KAAK;IACL,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;CACV,CAAC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAE/F,MAAM,mBAAmB,GAAG,mBAAmB,CAAC;AAEhD,MAAM,gBAAgB,GAAG,CAAC,IAAY,EAAU,EAAE;IAC9C,QAAQ,IAAI,EAAE,CAAC;QACX,KAAK,GAAG;YACJ,OAAO,SAAS,CAAC;QACrB,KAAK,GAAG;YACJ,OAAO,SAAS,CAAC;QACrB,KAAK,QAAQ;YACT,OAAO,SAAS,CAAC;QACrB,KAAK,QAAQ;YACT,OAAO,SAAS,CAAC;QACrB;YACI,OAAO,IAAI,CAAC;IACpB,CAAC;AACL,CAAC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,QAAQ,GAAgB,IAAI,GAAG,CAAC;IAClC,WAAW;IACX,OAAO;IACP,OAAO;IACP,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,UAAU;IACV,UAAU;IACV,SAAS;IACT,QAAQ;IACR,IAAI;IACJ,MAAM;IACN,MAAM;IACN,MAAM;IACN,QAAQ;IACR,SAAS;IACT,OAAO;IACP,SAAS;IACT,KAAK;IACL,UAAU;IACV,IAAI;IACJ,QAAQ;IACR,IAAI;IACJ,YAAY;IACZ,WAAW;IACX,KAAK;IACL,KAAK;IACL,MAAM;IACN,SAAS;IACT,SAAS;IACT,WAAW;IACX,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,MAAM;IACN,OAAO;IACP,MAAM;IACN,KAAK;IACL,QAAQ;IACR,KAAK;IACL,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;CACV,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAErG;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,kBAAkB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;AAEjG,MAAM,cAAc,GAA2B;IAC3C,GAAG,EAAE,SAAS;IACd,GAAG,EAAE,SAAS;IACd,QAAQ,EAAE,SAAS;IACnB,QAAQ,EAAE,SAAS;CACtB,CAAC;AAEF,MAAM,mBAAmB,GAAG,IAAI,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAEzF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAa,EAAU,EAAE,CACzD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC","sourcesContent":["import { toCamelCase } from \"./string.js\";\n\nconst RESERVED: Set<string> = new Set([\n \"arguments\",\n \"await\",\n \"break\",\n \"case\",\n \"catch\",\n \"class\",\n \"const\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"delete\",\n \"do\",\n \"else\",\n \"enum\",\n \"eval\",\n \"export\",\n \"extends\",\n \"false\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"import\",\n \"in\",\n \"instanceof\",\n \"interface\",\n \"let\",\n \"new\",\n \"null\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"return\",\n \"static\",\n \"super\",\n \"switch\",\n \"this\",\n \"throw\",\n \"true\",\n \"try\",\n \"typeof\",\n \"var\",\n \"void\",\n \"while\",\n \"with\",\n \"yield\",\n]);\n\n/**\n * Returns the name unchanged, or with a trailing underscore when it collides with a reserved word,\n * so it is safe to emit as a JavaScript identifier.\n *\n * @param name The candidate identifier.\n */\nexport const sanitizeIdentifier = (name: string): string => (RESERVED.has(name) ? `${name}_` : name);\n\n/**\n * Converts a name to camelCase and sanitizes it into a valid JavaScript identifier.\n *\n * @param name The name to convert.\n */\nexport const toCamelIdentifier = (name: string): string => sanitizeIdentifier(toCamelCase(name));\n\nconst SOURCE_ESCAPES: Record<string, string> = {\n \"<\": \"\\\\u003C\",\n \">\": \"\\\\u003E\",\n \"\\u2028\": \"\\\\u2028\",\n \"\\u2029\": \"\\\\u2029\",\n};\n\nconst UNSAFE_SOURCE_CHARS = new RegExp(`[${Object.keys(SOURCE_ESCAPES).join(\"\")}]`, \"g\");\n\n/**\n * Encodes a string as a JavaScript string literal, additionally escaping characters that are unsafe\n * to embed in generated source (angle brackets and the line and paragraph separators).\n *\n * @param value The string to encode.\n */\nexport const sourceStringLiteral = (value: string): string =>\n JSON.stringify(value).replace(UNSAFE_SOURCE_CHARS, (char) => SOURCE_ESCAPES[char] ?? char);\n"]}
package/dist/string.d.ts CHANGED
@@ -1,59 +1,32 @@
1
1
  /**
2
- * Pure, runtime-agnostic string-case helpers.
2
+ * Returns the string with its first character uppercased.
3
3
  *
4
- * The conversions translate between snake_case, kebab-case, camelCase, and
5
- * PascalCase. They split only on underscores and hyphens and preserve the case
6
- * of each segment, so they are not a substitute for a full Unicode-aware case
7
- * transform.
4
+ * @param value The string to transform.
8
5
  */
6
+ export declare const upperFirst: (value: string) => string;
9
7
  /**
10
- * Uppercases the first character of `value`, leaving the remaining characters
11
- * untouched.
8
+ * Returns the string with its first character lowercased.
12
9
  *
13
- * The tail is preserved verbatim rather than lowercased, so
14
- * `toUpperFirst("fooBar")` is `"FooBar"` and `toUpperFirst("URL")` is `"URL"`. An
15
- * empty string returns an empty string.
16
- *
17
- * @param value - The string to transform.
18
- * @returns `value` with its first character uppercased.
10
+ * @param value The string to transform.
19
11
  */
20
- export declare const toUpperFirst: (value: string) => string;
12
+ export declare const lowerFirst: (value: string) => string;
21
13
  /**
22
- * Converts a snake_case or kebab-case string to camelCase.
23
- *
24
- * The input is split on underscores and hyphens, dropping empty segments from
25
- * leading, trailing, or repeated separators. The first segment is kept
26
- * verbatim and every later segment is {@link toUpperFirst}-cased before joining,
27
- * so `toCamelCase("icon_name")` is `"iconName"` and `toCamelCase("Box")` is
28
- * `"Box"`. A string with no separators is returned unchanged.
14
+ * Converts an underscore- or hyphen-delimited string to camelCase.
29
15
  *
30
- * @param input - The snake_case or kebab-case identifier.
31
- * @returns The camelCase form of `input`.
16
+ * @param input The string to convert.
32
17
  */
33
18
  export declare const toCamelCase: (input: string) => string;
34
19
  /**
35
- * Converts a snake_case, kebab-case, or already-PascalCase string to
36
- * PascalCase.
20
+ * Converts an underscore- or hyphen-delimited string to PascalCase.
37
21
  *
38
- * The input is split on underscores and hyphens, dropping empty segments, and
39
- * every remaining segment is {@link toUpperFirst}-cased before joining, so
40
- * `toPascalCase("icon_name")` is `"IconName"` and `toPascalCase("Box")` is
41
- * `"Box"`. An empty string is returned unchanged.
42
- *
43
- * @param input - The identifier to transform.
44
- * @returns The PascalCase form of `input`.
22
+ * @param input The string to convert.
45
23
  */
46
24
  export declare const toPascalCase: (input: string) => string;
47
25
  /**
48
- * Converts a camelCase or PascalCase string to kebab-case.
49
- *
50
- * Each uppercase character is lowercased; every uppercase character other than
51
- * the first is additionally prefixed with a hyphen, so `toKebabCase("iconName")`
52
- * is `"icon-name"` and `toKebabCase("Title")` is `"title"`. The leading
53
- * character is never prefixed with a hyphen.
26
+ * Converts a camelCase or PascalCase string to kebab-case by lowercasing uppercase letters and
27
+ * inserting hyphens before interior ones.
54
28
  *
55
- * @param input - The camelCase or PascalCase identifier.
56
- * @returns The kebab-case form of `input`.
29
+ * @param input The string to convert.
57
30
  */
58
31
  export declare const toKebabCase: (input: string) => string;
59
32
  //# sourceMappingURL=string.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"string.d.ts","sourceRoot":"","sources":["../src/string.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,MAAwD,CAAC;AAEtG;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MAM3C,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,MAK5C,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MAC0E,CAAC"}
1
+ {"version":3,"file":"string.d.ts","sourceRoot":"","sources":["../src/string.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,UAAU,UAAW,MAAM,KAAG,MAAwD,CAAC;AAEpG;;;;GAIG;AACH,eAAO,MAAM,UAAU,UAAW,MAAM,KAAG,MAAwD,CAAC;AAIpG;;;;GAIG;AACH,eAAO,MAAM,WAAW,UAAW,MAAM,KAAG,MAI3C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,UAAW,MAAM,KAAG,MAI5C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,UAAW,MAAM,KAAG,MAC0E,CAAC"}
package/dist/string.js CHANGED
@@ -1,73 +1,43 @@
1
1
  /**
2
- * Pure, runtime-agnostic string-case helpers.
2
+ * Returns the string with its first character uppercased.
3
3
  *
4
- * The conversions translate between snake_case, kebab-case, camelCase, and
5
- * PascalCase. They split only on underscores and hyphens and preserve the case
6
- * of each segment, so they are not a substitute for a full Unicode-aware case
7
- * transform.
4
+ * @param value The string to transform.
8
5
  */
6
+ export const upperFirst = (value) => value.charAt(0).toUpperCase() + value.slice(1);
9
7
  /**
10
- * Uppercases the first character of `value`, leaving the remaining characters
11
- * untouched.
8
+ * Returns the string with its first character lowercased.
12
9
  *
13
- * The tail is preserved verbatim rather than lowercased, so
14
- * `toUpperFirst("fooBar")` is `"FooBar"` and `toUpperFirst("URL")` is `"URL"`. An
15
- * empty string returns an empty string.
16
- *
17
- * @param value - The string to transform.
18
- * @returns `value` with its first character uppercased.
10
+ * @param value The string to transform.
19
11
  */
20
- export const toUpperFirst = (value) => value.charAt(0).toUpperCase() + value.slice(1);
12
+ export const lowerFirst = (value) => value.charAt(0).toLowerCase() + value.slice(1);
13
+ const splitWords = (input) => input.split(/[_-]/g).filter((part) => part.length > 0);
21
14
  /**
22
- * Converts a snake_case or kebab-case string to camelCase.
23
- *
24
- * The input is split on underscores and hyphens, dropping empty segments from
25
- * leading, trailing, or repeated separators. The first segment is kept
26
- * verbatim and every later segment is {@link toUpperFirst}-cased before joining,
27
- * so `toCamelCase("icon_name")` is `"iconName"` and `toCamelCase("Box")` is
28
- * `"Box"`. A string with no separators is returned unchanged.
15
+ * Converts an underscore- or hyphen-delimited string to camelCase.
29
16
  *
30
- * @param input - The snake_case or kebab-case identifier.
31
- * @returns The camelCase form of `input`.
17
+ * @param input The string to convert.
32
18
  */
33
19
  export const toCamelCase = (input) => {
34
- const parts = input.split(/[_-]/g).filter((part) => part.length > 0);
20
+ const parts = splitWords(input);
35
21
  if (parts.length === 0)
36
22
  return input;
37
- const [first, ...rest] = parts;
38
- const head = first ?? "";
39
- return head + rest.map(toUpperFirst).join("");
23
+ return parts.map((part, index) => (index === 0 ? part : upperFirst(part))).join("");
40
24
  };
41
25
  /**
42
- * Converts a snake_case, kebab-case, or already-PascalCase string to
43
- * PascalCase.
44
- *
45
- * The input is split on underscores and hyphens, dropping empty segments, and
46
- * every remaining segment is {@link toUpperFirst}-cased before joining, so
47
- * `toPascalCase("icon_name")` is `"IconName"` and `toPascalCase("Box")` is
48
- * `"Box"`. An empty string is returned unchanged.
26
+ * Converts an underscore- or hyphen-delimited string to PascalCase.
49
27
  *
50
- * @param input - The identifier to transform.
51
- * @returns The PascalCase form of `input`.
28
+ * @param input The string to convert.
52
29
  */
53
30
  export const toPascalCase = (input) => {
54
- if (input.length === 0)
55
- return input;
56
- const parts = input.split(/[_-]/g).filter((part) => part.length > 0);
31
+ const parts = splitWords(input);
57
32
  if (parts.length === 0)
58
33
  return input;
59
- return parts.map(toUpperFirst).join("");
34
+ return parts.map(upperFirst).join("");
60
35
  };
61
36
  /**
62
- * Converts a camelCase or PascalCase string to kebab-case.
63
- *
64
- * Each uppercase character is lowercased; every uppercase character other than
65
- * the first is additionally prefixed with a hyphen, so `toKebabCase("iconName")`
66
- * is `"icon-name"` and `toKebabCase("Title")` is `"title"`. The leading
67
- * character is never prefixed with a hyphen.
37
+ * Converts a camelCase or PascalCase string to kebab-case by lowercasing uppercase letters and
38
+ * inserting hyphens before interior ones.
68
39
  *
69
- * @param input - The camelCase or PascalCase identifier.
70
- * @returns The kebab-case form of `input`.
40
+ * @param input The string to convert.
71
41
  */
72
42
  export const toKebabCase = (input) => input.replaceAll(/[A-Z]/g, (char, index) => (index === 0 ? char.toLowerCase() : `-${char.toLowerCase()}`));
73
43
  //# sourceMappingURL=string.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"string.js","sourceRoot":"","sources":["../src/string.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEtG;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE;IACjD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrC,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC;IAC/B,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;IACzB,OAAO,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAClD,CAAC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAU,EAAE;IAClD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrC,OAAO,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC5C,CAAC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE,CACjD,KAAK,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAa,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"string.js","sourceRoot":"","sources":["../src/string.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEpG;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEpG,MAAM,UAAU,GAAG,CAAC,KAAa,EAAY,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAEvG;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE;IACjD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxF,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAU,EAAE;IAClD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrC,OAAO,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC1C,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE,CACjD,KAAK,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAa,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC","sourcesContent":["/**\n * Returns the string with its first character uppercased.\n *\n * @param value The string to transform.\n */\nexport const upperFirst = (value: string): string => value.charAt(0).toUpperCase() + value.slice(1);\n\n/**\n * Returns the string with its first character lowercased.\n *\n * @param value The string to transform.\n */\nexport const lowerFirst = (value: string): string => value.charAt(0).toLowerCase() + value.slice(1);\n\nconst splitWords = (input: string): string[] => input.split(/[_-]/g).filter((part) => part.length > 0);\n\n/**\n * Converts an underscore- or hyphen-delimited string to camelCase.\n *\n * @param input The string to convert.\n */\nexport const toCamelCase = (input: string): string => {\n const parts = splitWords(input);\n if (parts.length === 0) return input;\n return parts.map((part, index) => (index === 0 ? part : upperFirst(part))).join(\"\");\n};\n\n/**\n * Converts an underscore- or hyphen-delimited string to PascalCase.\n *\n * @param input The string to convert.\n */\nexport const toPascalCase = (input: string): string => {\n const parts = splitWords(input);\n if (parts.length === 0) return input;\n return parts.map(upperFirst).join(\"\");\n};\n\n/**\n * Converts a camelCase or PascalCase string to kebab-case by lowercasing uppercase letters and\n * inserting hyphens before interior ones.\n *\n * @param input The string to convert.\n */\nexport const toKebabCase = (input: string): string =>\n input.replaceAll(/[A-Z]/g, (char, index: number) => (index === 0 ? char.toLowerCase() : `-${char.toLowerCase()}`));\n"]}
package/package.json CHANGED
@@ -1,14 +1,22 @@
1
1
  {
2
2
  "name": "@gtkx/utils",
3
- "version": "0.21.0",
4
- "description": "Cross-package utilities shared by GTKX runtime entry points",
3
+ "version": "1.0.0-rc.1",
4
+ "description": "Cross-package utilities shared by the GTKX packages and toolchain: logging, error formatting, string and class helpers",
5
+ "keywords": [
6
+ "gtkx",
7
+ "gtk",
8
+ "gtk4",
9
+ "utils",
10
+ "linux",
11
+ "desktop"
12
+ ],
5
13
  "homepage": "https://gtkx.dev",
6
14
  "bugs": {
7
15
  "url": "https://github.com/gtkx-org/gtkx/issues"
8
16
  },
9
17
  "repository": {
10
18
  "type": "git",
11
- "url": "https://github.com/gtkx-org/gtkx.git",
19
+ "url": "git+https://github.com/gtkx-org/gtkx.git",
12
20
  "directory": "packages/utils"
13
21
  },
14
22
  "license": "MPL-2.0",
@@ -17,7 +25,6 @@
17
25
  "exports": {
18
26
  "./package.json": "./package.json",
19
27
  ".": {
20
- "source": "./src/index.ts",
21
28
  "types": "./dist/index.d.ts",
22
29
  "default": "./dist/index.js"
23
30
  }
@@ -27,9 +34,15 @@
27
34
  "dist",
28
35
  "src"
29
36
  ],
37
+ "dependencies": {
38
+ "picocolors": "^1.1.1"
39
+ },
40
+ "engines": {
41
+ "node": ">=24"
42
+ },
30
43
  "scripts": {
31
- "build": "tsc -b",
32
- "test": "vitest run",
33
- "typecheck": "tsc -b --emitDeclarationOnly"
44
+ "build": "tsc -b tsconfig.lib.json",
45
+ "typecheck": "tsc -b --emitDeclarationOnly",
46
+ "release": "tsx ../../scripts/release-package.ts"
34
47
  }
35
48
  }
package/src/class.ts CHANGED
@@ -1,17 +1,36 @@
1
1
  /**
2
- * Structural type matching any class abstract or concrete whose instances
3
- * have type `T`.
4
- *
5
- * A class value carries a construct signature plus a `prototype`, but a bare
6
- * construct signature types `prototype` as `any`. The `& { prototype: T }`
7
- * intersection recovers the precise instance type so callers can read
8
- * `cls.prototype` as `T`, and the `abstract new` form accepts both abstract and
9
- * concrete classes, making this the widest supertype of every class value. The
10
- * `never[]` constructor parameters admit any class while documenting that the
11
- * type is used as an identity token, not to construct instances.
2
+ * A constructor value of any (possibly abstract) class producing instances of `T`.
12
3
  */
13
4
  export type AnyClass<T extends object = object> = (abstract new (
14
5
  ...args: never[]
15
6
  ) => T) & {
16
- readonly prototype: T;
7
+ prototype: T;
17
8
  };
9
+
10
+ /**
11
+ * Returns the direct superclass of `cls`, or `null` when it has no class ancestor.
12
+ *
13
+ * @param cls The class whose prototype parent to inspect.
14
+ */
15
+ export function getParentClass(cls: AnyClass): AnyClass | null {
16
+ const parent: unknown = Object.getPrototypeOf(cls);
17
+ return typeof parent === "function" && parent !== Function.prototype ? (parent as AnyClass) : null;
18
+ }
19
+
20
+ /**
21
+ * Walks the class chain from `cls` up through its ancestors, calling `visit` on each and
22
+ * returning the first result that is not `undefined`.
23
+ *
24
+ * @param cls The class to start from, or `null` to visit nothing.
25
+ * @param visit Called with each class in the chain; a defined return value stops the walk.
26
+ * @returns The first defined result from `visit`, or `undefined` if none was produced.
27
+ */
28
+ export function walkClassChain<T>(cls: AnyClass | null, visit: (ancestor: AnyClass) => T | undefined): T | undefined {
29
+ let current = cls;
30
+ while (current !== null) {
31
+ const result = visit(current);
32
+ if (result !== undefined) return result;
33
+ current = getParentClass(current);
34
+ }
35
+ return undefined;
36
+ }
package/src/collection.ts CHANGED
@@ -1,103 +1,65 @@
1
1
  /**
2
- * Pure, runtime-agnostic collection helpers shared across GTKX packages.
3
- */
4
-
5
- /**
6
- * Returns a shallow copy of `record` with the given `keys` removed.
7
- *
8
- * Keys absent from `record` are ignored, and `record` is not mutated. The
9
- * result is typed as the input shape because callers treat the excluded keys
10
- * as runtime-only concerns absent from the static type.
2
+ * Returns the items with duplicates removed, keeping the first item for each distinct key.
11
3
  *
12
- * @typeParam T - The record shape.
13
- * @param record - The source object.
14
- * @param keys - The keys to exclude from the copy.
15
- * @returns A new object holding every own enumerable key of `record` except
16
- * those listed in `keys`.
4
+ * @param items The items to deduplicate.
5
+ * @param key Computes the identity string used to detect duplicates.
17
6
  */
18
- export const omit = <T extends Record<string, unknown>>(record: T, keys: readonly string[]): T => {
19
- const result: Record<string, unknown> = {};
20
- for (const key of Object.keys(record)) {
21
- if (!keys.includes(key)) {
22
- result[key] = record[key];
23
- }
7
+ export const uniqBy = <T>(items: T[], key: (item: T) => string): T[] => {
8
+ const seen = new Set<string>();
9
+ const result: T[] = [];
10
+ for (const item of items) {
11
+ const identity = key(item);
12
+ if (seen.has(identity)) continue;
13
+ seen.add(identity);
14
+ result.push(item);
24
15
  }
25
- return result as T;
16
+ return result;
26
17
  };
27
18
 
28
- /**
29
- * Compares two arrays of primitives for element-wise strict equality.
30
- *
31
- * Returns `true` when both arguments are the same reference (including both
32
- * being `null` or `undefined`), or when they have equal length and every
33
- * element is strictly equal (`===`) at the same index. A `null`/`undefined`
34
- * argument is equal only to another `null`/`undefined` argument.
35
- *
36
- * @typeParam T - The primitive element type.
37
- * @param a - The first array, or `null`/`undefined`.
38
- * @param b - The second array, or `null`/`undefined`.
39
- * @returns Whether the two arrays are shallowly equal.
40
- */
41
- export const isShallowEqual = <T extends string | number | boolean>(
42
- a: readonly T[] | null | undefined,
43
- b: readonly T[] | null | undefined,
44
- ): boolean => {
45
- if (a === b) return true;
46
- if (!a || !b) return false;
19
+ export const isSameArray = <T>(a: T[], b: T[]): boolean => {
47
20
  if (a.length !== b.length) return false;
48
- for (let i = 0; i < a.length; i++) {
49
- if (a[i] !== b[i]) return false;
21
+ for (let index = 0; index < a.length; index++) {
22
+ if (a[index] !== b[index]) return false;
50
23
  }
51
24
  return true;
52
25
  };
53
26
 
27
+ const compareStrings = (a: string, b: string): number => a.localeCompare(b);
28
+
54
29
  /**
55
- * Compares two arrays of records by shallow per-element equality.
30
+ * Returns a new array of the given strings sorted with locale-aware comparison.
56
31
  *
57
- * Returns `true` when both arrays have equal length and, at every index, the
58
- * two records expose the same set of keys with strictly equal (`===`) values.
59
- * Values are compared one level deep only; nested objects are compared by
60
- * reference.
32
+ * @param values The strings to sort.
33
+ */
34
+ export const sortStrings = (values: Iterable<string>): string[] => [...values].sort(compareStrings);
35
+
36
+ /**
37
+ * Returns a new array of the given items sorted by a locale-aware comparison of their keys.
61
38
  *
62
- * @typeParam T - The record element type.
63
- * @param a - The first array of records.
64
- * @param b - The second array of records.
65
- * @returns Whether the two arrays are element-wise shallowly equal.
39
+ * @param items The items to sort.
40
+ * @param key Computes the string to sort each item by.
66
41
  */
67
- export const isShallowArrayEqual = <T extends Record<string, unknown>>(a: readonly T[], b: readonly T[]): boolean => {
68
- if (a.length !== b.length) return false;
42
+ export const sortStringsBy = <T>(items: Iterable<T>, key: (item: T) => string): T[] =>
43
+ [...items].sort((a, b) => compareStrings(key(a), key(b)));
69
44
 
70
- for (let i = 0; i < a.length; i++) {
71
- const itemA = a[i];
72
- const itemB = b[i];
73
- if (!itemA || !itemB) return false;
45
+ /**
46
+ * Determines whether two records have the same keys and strictly equal values.
47
+ *
48
+ * @param a The first record, or `undefined`.
49
+ * @param b The second record, or `undefined`.
50
+ * @returns `true` when both are the same reference, or both are defined with identical own keys and values.
51
+ */
52
+ export const isShallowEqual = <T extends Record<string, unknown>>(a?: T, b?: T): boolean => {
53
+ if (a === b) return true;
54
+ if (!a || !b) return false;
74
55
 
75
- const keysA = Object.keys(itemA);
76
- const keysB = Object.keys(itemB);
77
- if (keysA.length !== keysB.length) return false;
56
+ const keysA = Object.keys(a);
57
+ const keysB = Object.keys(b);
58
+ if (keysA.length !== keysB.length) return false;
78
59
 
79
- for (const key of keysA) {
80
- if (itemA[key] !== itemB[key]) return false;
81
- }
60
+ for (const key of keysA) {
61
+ if (a[key] !== b[key]) return false;
82
62
  }
83
63
 
84
64
  return true;
85
65
  };
86
-
87
- /**
88
- * Builds a reverse lookup from a numeric enum's values to their member names.
89
- *
90
- * A TypeScript numeric enum's runtime object carries both name-to-value and
91
- * value-to-name entries; this keeps only the name-to-value direction and
92
- * inverts it, yielding a `Map` from each numeric value to the name that
93
- * declared it.
94
- *
95
- * @param enumObject - A numeric enum's runtime object.
96
- * @returns A map from each numeric enum value to its declared member name.
97
- */
98
- export const reverseNumericEnum = (enumObject: Record<string, string | number>): Map<number, string> =>
99
- new Map<number, string>(
100
- Object.entries(enumObject)
101
- .filter((entry): entry is [string, number] => typeof entry[1] === "number")
102
- .map(([name, value]) => [value, name]),
103
- );