@stonyx/utils 0.1.1 → 0.2.0

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/README.md CHANGED
@@ -1,33 +1,225 @@
1
1
  # stonyx-utils
2
2
 
3
- # TODO: Generate documentation for all utils
3
+ Utilities module for the Stonyx Framework. Provides helpers for files, objects, strings, dates, and promises.
4
4
 
5
- ## Running the test suite
5
+ ---
6
+
7
+ ## Quick Reference Table
8
+
9
+ | Category | Function | Description |
10
+ | ----------- | ----------------------- | -------------------------------------------------------- |
11
+ | **File** | `createFile` | Create a file (supports JSON serialization). |
12
+ | | `updateFile` | Atomically update a file. |
13
+ | | `copyFile` | Copy a file, with optional overwrite. |
14
+ | | `readFile` | Read a file (JSON optional), with missing file callback. |
15
+ | | `deleteFile` | Delete a file (ignore missing optional). |
16
+ | | `deleteDirectory` | Recursively delete a directory. |
17
+ | | `createDirectory` | Recursively create a directory. |
18
+ | | `forEachFileImport` | Dynamically import all JS files in a directory. |
19
+ | | `fileExists` | Check if a file exists. |
20
+ | **Object** | `deepCopy` | Deep clone an object or array. |
21
+ | | `objToJson` | Convert object to formatted JSON string. |
22
+ | | `makeArray` | Wrap a value in an array. |
23
+ | | `mergeObject` | Deep merge two objects (ignore new keys optional). |
24
+ | | `get` | Get nested property safely via dot path. |
25
+ | | `getOrSet` | Get or set value in a Map. |
26
+ | **String** | `kebabCaseToCamelCase` | Convert kebab-case to camelCase. |
27
+ | | `kebabCaseToPascalCase` | Convert kebab-case to PascalCase. |
28
+ | | `generateRandomString` | Generate a random alphanumeric string. |
29
+ | | `pluralize` | Return plural form of English nouns. |
30
+ | **Date** | `getTimestamp` | Return current UNIX timestamp in seconds. |
31
+ | **Promise** | `sleep` | Async delay for a given number of seconds. |
32
+
33
+ ---
34
+
35
+ ## Table of Contents
36
+
37
+ * [Installation](#installation)
38
+ * [Running the Test Suite](#running-the-test-suite)
39
+ * [File Utils](#file-utils)
40
+ * [Object Utils](#object-utils)
41
+ * [String Utils](#string-utils)
42
+ * [Date Utils](#date-utils)
43
+ * [Promise Utils](#promise-utils)
44
+ * [License](#license)
45
+
46
+ ---
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ npm install @stonyx/utils
6
52
  ```
7
- npm test
53
+
54
+ ---
55
+
56
+ ## File Utils
57
+
58
+ The File utils wrap the `path` and `fs` libraries to manipulate the local file system asynchronously with full async/await support. Includes creation, reading, updating, copying, deleting files and directories, checking existence, and dynamic importing via `forEachFileImport`.
59
+
60
+ ### Functions
61
+
62
+ #### `createFile(filePath, data, options={})`
63
+
64
+ Creates a file at the given path.
65
+
66
+ * `options.json` — boolean, if true, data will be serialized to JSON.
67
+
68
+ #### `updateFile(filePath, data, options={})`
69
+
70
+ Updates a file atomically by writing to a temporary file first.
71
+
72
+ * `options.json` — boolean, serialize as JSON.
73
+
74
+ #### `copyFile(sourcePath, targetPath, options={})`
75
+
76
+ Copies a file from source to target.
77
+
78
+ * `options.overwrite` — boolean, default false.
79
+
80
+ #### `readFile(filePath, options={})`
81
+
82
+ Reads a file, optionally parsing JSON.
83
+
84
+ * `options.json` — boolean
85
+ * `options.missingFileCallback` — function called if file doesn’t exist.
86
+
87
+ #### `deleteFile(filePath, options={})`
88
+
89
+ Deletes a file.
90
+
91
+ * `options.ignoreAccessFailure` — boolean, ignores errors if file missing.
92
+
93
+ #### `deleteDirectory(dir)`
94
+
95
+ Recursively deletes a directory.
96
+
97
+ #### `createDirectory(dir)`
98
+
99
+ Recursively creates a directory.
100
+
101
+ #### `forEachFileImport(dir, callback, options={})`
102
+
103
+ Dynamically imports all `.js` files in a directory and calls `callback(exports, details)`.
104
+
105
+ | Option | Type | Default | Description |
106
+ | :-------------------: | :-----: | :-----: | :-------------------------------------------------------- |
107
+ | `fullExport` | Boolean | false | If true, callback receives all exports, not just default. |
108
+ | `rawName` | Boolean | false | If true, the file name is not converted to camelCase. |
109
+ | `ignoreAccessFailure` | Boolean | false | If true, directory access errors are ignored. |
110
+
111
+ Example:
112
+
113
+ ```js
114
+ import { forEachFileImport } from '@stonyx/utils/file';
115
+
116
+ await forEachFileImport('./utils', (exports, details) => {
117
+ console.log(details.name, exports);
118
+ }, { fullExport: true });
8
119
  ```
9
120
 
10
- ## File utils
121
+ #### `fileExists(filePath)`
122
+
123
+ Returns `true` if file exists, else `false`.
124
+
125
+ ---
126
+
127
+ ## Object Utils
128
+
129
+ Helpers for working with objects and arrays.
11
130
 
12
- TODO: Update documentation to instruct a broader audience
131
+ ### Functions
13
132
 
14
- The File utils wrap the `path` and `fs` library to allow consuming classes to manipulate the local file system with full async/await support. Additionally it exposes the `forEachFileImport` method which lets us dynamically and flexibly import dependencies.
133
+ #### `deepCopy(obj)`
15
134
 
16
- ### Usage example
135
+ Returns a deep copy of an object or array.
136
+
137
+ #### `objToJson(obj, format='\t')`
138
+
139
+ Returns a formatted JSON string.
140
+
141
+ #### `makeArray(obj)`
142
+
143
+ Wraps a value in an array if it isn’t already one.
144
+
145
+ #### `mergeObject(obj1, obj2, options={})`
146
+
147
+ Deep merges two objects.
148
+
149
+ * `options.ignoreNewKeys` — boolean, if true, keys not in `obj1` are ignored.
150
+
151
+ #### `get(obj, path)`
152
+
153
+ Safely gets a nested property using dot notation. Returns `null` if path not found.
154
+
155
+ #### `getOrSet(map, key, defaultValue)`
156
+
157
+ Gets the value for a key in a `Map`, or sets it to `defaultValue` if missing.
158
+
159
+ ---
160
+
161
+ ## String Utils
162
+
163
+ ### Functions
164
+
165
+ #### `kebabCaseToCamelCase(str)`
166
+
167
+ Converts `'my-string'` → `'myString'`.
168
+
169
+ #### `kebabCaseToPascalCase(str)`
170
+
171
+ Converts `'my-string'` → `'MyString'`.
172
+
173
+ #### `generateRandomString(length=8)`
174
+
175
+ Generates a random alphanumeric string.
176
+
177
+ #### `pluralize(word)`
178
+
179
+ Returns the plural form of an English noun, handling irregulars and uncountable nouns.
180
+
181
+ Example:
182
+
183
+ ```js
184
+ import { pluralize } from '@stonyx/utils/string';
185
+
186
+ console.log(pluralize('person')); // people
187
+ console.log(pluralize('box')); // boxes
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Date Utils
193
+
194
+ ### Functions
195
+
196
+ #### `getTimestamp()`
197
+
198
+ Returns the current UNIX timestamp (seconds since epoch).
17
199
 
18
200
  ```js
19
- await forEachFileImport(targetDirectory, (exports, details) => {
20
- // Insert logic per export
21
- }, options);
201
+ import { getTimestamp } from '@stonyx/utils/date';
202
+
203
+ console.log(getTimestamp()); // e.g., 1693564800
22
204
  ```
23
205
 
24
- ### Valid Options
206
+ ---
207
+
208
+ ## Promise Utils
209
+
210
+ ### Functions
211
+
212
+ #### `sleep(seconds)`
213
+
214
+ Delays execution for the given number of seconds.
215
+
216
+ ```js
217
+ import { sleep } from '@stonyx/utils/promise';
218
+
219
+ await sleep(2); // waits 2 seconds
220
+ ```
25
221
 
26
- | Option | Type | Default | Description |
27
- | :---: | :---: | :---: | :--- |
28
- | `fullExport` | **Boolean** | *false* | When set to true, The `exports` parameter will be all exports, and not just the default one. |
29
- | `rawName` | **Boolean** | *false* | When set to true, `forEachFileImport` will not convert the file name to be camelCase and leave it raw instead |
30
- | `ignoreAccessFailure` | **Boolean** | *false* | When set to true, failure to load directory will be ignored |
222
+ ---
31
223
 
32
224
  ## License
33
225
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "keywords": [
4
4
  "stonyx-module"
5
5
  ],
6
- "version": "0.1.1",
6
+ "version": "0.2.0",
7
7
  "description": "Utils module for Stonyx Framework",
8
8
  "type": "module",
9
9
  "exports": {
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "devDependencies": {
25
25
  "fs": "^0.0.1-security",
26
- "qunit": "^2.24.1"
27
- },
28
- "dependencies": {}
26
+ "qunit": "^2.24.1",
27
+ "sinon": "^21.0.0"
28
+ }
29
29
  }
package/src/object.js CHANGED
@@ -2,14 +2,20 @@ export function deepCopy(obj) {
2
2
  return JSON.parse(JSON.stringify(obj));
3
3
  }
4
4
 
5
- export function objToJson(obj) {
6
- return JSON.stringify(obj);
5
+ export function objToJson(obj, format='\t') {
6
+ return JSON.stringify(obj, null, format);
7
7
  }
8
8
 
9
9
  export function makeArray(obj) {
10
10
  return Array.isArray(obj) ? obj : [obj];
11
11
  }
12
12
 
13
+ function cloneShallow(value) {
14
+ if (Array.isArray(value)) return value.slice();
15
+ if (value && typeof value === 'object') return { ...value };
16
+ return value;
17
+ }
18
+
13
19
  export function mergeObject(obj1, obj2, options={}) {
14
20
  if (Array.isArray(obj1) || Array.isArray(obj2)) throw new Error('Cannot merge arrays.');
15
21
 
@@ -32,8 +38,24 @@ export function mergeObject(obj1, obj2, options={}) {
32
38
  return result;
33
39
  }
34
40
 
35
- function cloneShallow(value) {
36
- if (Array.isArray(value)) return value.slice();
37
- if (value && typeof value === 'object') return { ...value };
38
- return value;
41
+ export function get(obj, path) {
42
+ if (arguments.length !== 2) return console.error('Get must be called with two arguments; an object and a property key.');
43
+ if (!obj) return console.error(`Cannot call get with '${path}' on an undefined object.`);
44
+ if (typeof path !== 'string') return console.error('The path provided to get must be a string.');
45
+
46
+ for (const key of path.split('.')) {
47
+ if (obj[key] === undefined) return null;
48
+
49
+ obj = obj[key];
50
+ }
51
+
52
+ return obj;
53
+ }
54
+
55
+ export function getOrSet(map, key, defaultValue) {
56
+ if (!(map instanceof Map)) throw new Error('First argument to getOrSet must be a Map.');
57
+
58
+ if (!map.has(key)) map.set(key, typeof defaultValue === "function" ? defaultValue() : defaultValue);
59
+
60
+ return map.get(key);
39
61
  }
@@ -0,0 +1,105 @@
1
+ // --- Irregular nouns ---
2
+ const irregular = {
3
+ person: 'people',
4
+ man: 'men',
5
+ woman: 'women',
6
+ child: 'children',
7
+ tooth: 'teeth',
8
+ foot: 'feet',
9
+ mouse: 'mice',
10
+ goose: 'geese',
11
+ ox: 'oxen',
12
+ cactus: 'cacti',
13
+ nucleus: 'nuclei',
14
+ syllabus: 'syllabi',
15
+ focus: 'foci',
16
+ fungus: 'fungi',
17
+ appendix: 'appendices',
18
+ index: 'indices',
19
+ criterion: 'criteria',
20
+ phenomenon: 'phenomena',
21
+ die: 'dice',
22
+ thesis: 'theses',
23
+ analysis: 'analyses',
24
+ crisis: 'crises',
25
+ radius: 'radii',
26
+ corpus: 'corpora',
27
+ };
28
+
29
+ // --- Uncountables ---
30
+ const uncountable = new Set([
31
+ 'sheep', 'fish', 'deer', 'series', 'species', 'news', 'information',
32
+ 'rice', 'moose', 'bison', 'salmon', 'aircraft', 'offspring'
33
+ ]);
34
+
35
+ // --- Exceptions ---
36
+ const fExceptions = new Set(['chief', 'roof', 'belief', 'chef', 'cliff', 'reef', 'proof', 'brief']);
37
+
38
+ // Keep only true irregular -o exceptions (consonant + o but take just "s")
39
+ const oExceptions = new Set(['piano', 'photo', 'halo', 'canto', 'solo']);
40
+
41
+ // --- Utility to preserve casing ---
42
+ function applyCasing(original, plural) {
43
+ if (original === original.toUpperCase()) return plural.toUpperCase();
44
+ if (original === original.toLowerCase()) return plural.toLowerCase();
45
+ if (original[0] === original[0].toUpperCase()) {
46
+ return plural.charAt(0).toUpperCase() + plural.slice(1);
47
+ }
48
+ return plural;
49
+ }
50
+
51
+ // --- Rule-based pluralization ---
52
+ const rules = [
53
+ // quiz → quizzes, waltz → waltzes, topaz → topazes
54
+ [/z$/i, w => (/iz$/i.test(w) ? w + 'zes' : w + 'es')],
55
+
56
+ // bus → buses, box → boxes, church → churches, but stomach → stomachs (exclude -ach)
57
+ [/(s|x|ch|sh)$/i, w => (/ach$/i.test(w) ? w + 's' : w + 'es')],
58
+
59
+ // vowel + y → +s (key → keys)
60
+ [/[aeiou]y$/i, w => w + 's'],
61
+
62
+ // consonant + y → -ies (city → cities)
63
+ [/y$/i, w => w.slice(0, -1) + 'ies'],
64
+
65
+ // -fe → -ves (knife → knives), but not chief/roof/etc
66
+ [/fe$/i, w => (fExceptions.has(w) ? w + 's' : w.slice(0, -2) + 'ves')],
67
+
68
+ // -f → -ves (wolf → wolves), but not cliff/etc
69
+ [/f$/i, w => (fExceptions.has(w) ? w + 's' : w.slice(0, -1) + 'ves')],
70
+
71
+ // -sis → -ses (analysis → analyses, thesis → theses)
72
+ [/sis$/i, w => w.slice(0, -2) + 'ses'],
73
+
74
+ // vowel + o → +s (zoo → zoos, video → videos, patio → patios)
75
+ [/[aeiou]o$/i, w => w + 's'],
76
+
77
+ // consonant + o → usually +es, unless in oExceptions
78
+ [/o$/i, w => (oExceptions.has(w) ? w + 's' : w + 'es')],
79
+
80
+ // default: just +s
81
+ [/$/i, w => w + 's']
82
+ ];
83
+
84
+ // --- Exported pluralizer ---
85
+ export default function pluralize(word) {
86
+ if (typeof word !== 'string' || !/^[a-zA-Z]+$/.test(word)) {
87
+ throw new Error('Input must be a single word containing only letters.');
88
+ }
89
+
90
+ const lower = word.toLowerCase();
91
+
92
+ if (uncountable.has(lower)) return word;
93
+
94
+ if (irregular[lower]) {
95
+ return applyCasing(word, irregular[lower]);
96
+ }
97
+
98
+ for (const [pattern, transform] of rules) {
99
+ if (pattern.test(lower)) {
100
+ return applyCasing(word, transform(lower));
101
+ }
102
+ }
103
+
104
+ return word; // fallback (shouldn't hit)
105
+ }
package/src/string.js CHANGED
@@ -23,11 +23,9 @@ export function kebabCaseToPascalCase(str) {
23
23
  return kebabToCase(str, true);
24
24
  }
25
25
 
26
- export function pluralize(str) {
27
- return `${str}s`;
28
- }
29
-
30
26
  export function generateRandomString(length=8) {
31
27
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
32
28
  return Array(length).fill('').map(() => characters.charAt(Math.floor(Math.random() * characters.length))).join('');
33
- }
29
+ }
30
+
31
+ export { default as pluralize } from './plurarize.js';