@wizhut_tech/wizjs 0.1.7 → 0.2.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.
@@ -0,0 +1,29 @@
1
+ const t = require('tap');
2
+
3
+ const { BadlyInitializedError } = require('../src/internal/exceptions.js');
4
+ const root = require('@wizhut_tech/wizjs');
5
+
6
+
7
+ t.test('exceptions/BadlyInitializedError behaves like an Error', (t) => {
8
+ const err = new BadlyInitializedError('not ready');
9
+
10
+ t.type(err, Error, 'extends Error');
11
+ t.type(err, BadlyInitializedError);
12
+ t.equal(err.message, 'not ready');
13
+ t.ok(err.stack, 'carries a stack');
14
+ t.end();
15
+ });
16
+
17
+
18
+ t.test('exceptions/BadlyInitializedError ignores trailing arguments', (t) => {
19
+ const err = new BadlyInitializedError('only the message', 'extra', 42);
20
+
21
+ t.equal(err.message, 'only the message');
22
+ t.end();
23
+ });
24
+
25
+
26
+ t.test('exceptions/BadlyInitializedError is the one the root import exposes', (t) => {
27
+ t.equal(root.exceptions.BadlyInitializedError, BadlyInitializedError);
28
+ t.end();
29
+ });
@@ -0,0 +1,41 @@
1
+ const t = require('tap');
2
+ const path = require('path');
3
+
4
+ const { loadFully } = require('../src/io/files.js');
5
+
6
+
7
+ t.test('files/loadFully returns null for a file that is not there', async (t) => {
8
+ const missing = path.join(t.testdir(), 'no-such-file.txt');
9
+
10
+ t.equal(await loadFully(missing), null);
11
+ });
12
+
13
+
14
+ t.test('files/loadFully returns null when the path is a directory', async (t) => {
15
+ const dir = t.testdir({ 'sub': {} });
16
+
17
+ t.equal(await loadFully(path.join(dir, 'sub')), null);
18
+ });
19
+
20
+
21
+ t.test('files/loadFully returns the whole file as text', async (t) => {
22
+ const dir = t.testdir({ 'hello.txt': 'contents\n' });
23
+
24
+ t.equal(await loadFully(path.join(dir, 'hello.txt')), 'contents\n');
25
+ });
26
+
27
+
28
+ t.test('files/loadFully returns an empty string for an empty file', async (t) => {
29
+ const dir = t.testdir({ 'empty.txt': '' });
30
+
31
+ t.equal(await loadFully(path.join(dir, 'empty.txt')), '',
32
+ 'an empty file is distinguishable from a failed read');
33
+ });
34
+
35
+
36
+ t.test('files/loadFully preserves multi-line and unicode content', async (t) => {
37
+ const body = 'first\nsecond\n\u03b1\u03b2\u03b3\n';
38
+ const dir = t.testdir({ 'multi.txt': body });
39
+
40
+ t.equal(await loadFully(path.join(dir, 'multi.txt')), body);
41
+ });
@@ -0,0 +1,49 @@
1
+ const t = require('tap');
2
+
3
+ const { if_exception } = require('../src/lang/flow.js');
4
+
5
+
6
+ t.test('flow/if_exception runs the success branch', (t) => {
7
+ const calls = [];
8
+
9
+ if_exception(
10
+ () => calls.push('body'),
11
+ () => calls.push('ok'),
12
+ () => calls.push('failed')
13
+ );
14
+
15
+ t.same(calls, ['body', 'ok']);
16
+ t.end();
17
+ });
18
+
19
+
20
+ t.test('flow/if_exception runs the failure branch with the error', (t) => {
21
+ const calls = [];
22
+ const boom = new Error('boom');
23
+ let seen = null;
24
+
25
+ if_exception(
26
+ () => { calls.push('body'); throw boom; },
27
+ () => calls.push('ok'),
28
+ (error) => { calls.push('failed'); seen = error; }
29
+ );
30
+
31
+ t.same(calls, ['body', 'failed'], 'the success callback is skipped');
32
+ t.equal(seen, boom, 'the thrown error is handed to the failure callback');
33
+ t.end();
34
+ });
35
+
36
+
37
+ t.test('flow/if_exception catches a throw from the success callback', (t) => {
38
+ const boom = new Error('from fn_noexc');
39
+ let seen = null;
40
+
41
+ if_exception(
42
+ () => {},
43
+ () => { throw boom; },
44
+ (error) => { seen = error; }
45
+ );
46
+
47
+ t.equal(seen, boom, 'fn_noexc runs inside the same try block');
48
+ t.end();
49
+ });
@@ -0,0 +1,79 @@
1
+ const t = require('tap');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const root = require('@wizhut_tech/wizjs');
6
+
7
+ const PUBLIC_NAMESPACES = ['io', 'lang', 'math'];
8
+ const SRC = path.join(__dirname, '..', 'src');
9
+
10
+
11
+ function areasOf(namespace) {
12
+ return fs.readdirSync(path.join(SRC, namespace))
13
+ .filter((name) => name.endsWith('.js'))
14
+ .map((name) => name.slice(0, -3))
15
+ .sort();
16
+ }
17
+
18
+
19
+ t.test('exports/every public leaf module has a subpath', (t) => {
20
+ for (const namespace of PUBLIC_NAMESPACES) {
21
+ const areas = areasOf(namespace);
22
+ t.ok(areas.length > 0, `${namespace} has at least one area`);
23
+
24
+ for (const area of areas) {
25
+ const subpath = `@wizhut_tech/wizjs/${namespace}/${area}`;
26
+
27
+ t.equal(
28
+ require(subpath),
29
+ root[namespace][area],
30
+ `${subpath} is the same object as root.${namespace}.${area}`
31
+ );
32
+
33
+ t.equal(
34
+ require(`${subpath}.js`),
35
+ root[namespace][area],
36
+ `${subpath}.js resolves to the same object`
37
+ );
38
+ }
39
+ }
40
+
41
+ t.end();
42
+ });
43
+
44
+
45
+ t.test('exports/root entry point still resolves', (t) => {
46
+ t.equal(require('@wizhut_tech/wizjs'), root);
47
+ t.type(root.lang.collections.Counter, 'function');
48
+ t.type(root.exceptions.BadlyInitializedError, 'function');
49
+ t.end();
50
+ });
51
+
52
+
53
+ t.test('exports/internals stay private', (t) => {
54
+ t.throws(
55
+ () => require('@wizhut_tech/wizjs/internal/exceptions'),
56
+ { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' },
57
+ 'src/internal is not reachable as a subpath'
58
+ );
59
+
60
+ t.throws(
61
+ () => require('@wizhut_tech/wizjs/src/lang/collections.js'),
62
+ { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' },
63
+ 'the raw src/ layout is not part of the public surface'
64
+ );
65
+
66
+ t.throws(
67
+ () => require('@wizhut_tech/wizjs/lang/does-not-exist'),
68
+ { code: 'MODULE_NOT_FOUND' },
69
+ 'an unknown area under a mapped namespace fails to resolve'
70
+ );
71
+
72
+ t.end();
73
+ });
74
+
75
+
76
+ t.test('exports/package.json is reachable', (t) => {
77
+ t.equal(require('@wizhut_tech/wizjs/package.json').name, '@wizhut_tech/wizjs');
78
+ t.end();
79
+ });
package/package.json CHANGED
@@ -5,7 +5,20 @@
5
5
  },
6
6
  "description": "Simple but useful hacks for everyday javascript programming",
7
7
  "devDependencies": {
8
- "tap": "21.7.5"
8
+ "tap": "21.8.0"
9
+ },
10
+ "engines": {
11
+ "node": ">=14.13.0"
12
+ },
13
+ "exports": {
14
+ ".": "./index.js",
15
+ "./io/*": "./src/io/*.js",
16
+ "./io/*.js": "./src/io/*.js",
17
+ "./lang/*": "./src/lang/*.js",
18
+ "./lang/*.js": "./src/lang/*.js",
19
+ "./math/*": "./src/math/*.js",
20
+ "./math/*.js": "./src/math/*.js",
21
+ "./package.json": "./package.json"
9
22
  },
10
23
  "homepage": "https://github.com/wizhut/wizjs#readme",
11
24
  "keywords": [
@@ -24,5 +37,5 @@
24
37
  "scripts": {
25
38
  "test": "tap run"
26
39
  },
27
- "version": "0.1.7"
40
+ "version": "0.2.1"
28
41
  }
package/readme.md CHANGED
@@ -2,9 +2,20 @@
2
2
 
3
3
  A Javascript library that ports selected Python idioms and stdlib helpers (`itertools`, `functools`, `collections`) to everyday JS. A few selected dependencies only. No, this library will not become another `lodash` :).
4
4
 
5
- Use by importing:
5
+ Use by importing the area you need:
6
6
 
7
- `const wizjs = require('@wizhut_tech/wizjs')`. Returns an object structured like:
7
+ ```js
8
+ const { isNil } = require('@wizhut_tech/wizjs/lang/checks');
9
+ const { Counter } = require('@wizhut_tech/wizjs/lang/collections');
10
+ const { clamp } = require('@wizhut_tech/wizjs/math/numbers');
11
+ ```
12
+
13
+ Every area listed below is reachable as `@wizhut_tech/wizjs/<namespace>/<area>`.
14
+ The subpath form needs Node 14.13 or newer.
15
+
16
+ The whole library is also available from a single root import --
17
+ `const wizjs = require('@wizhut_tech/wizjs')` -- which returns an object
18
+ structured like:
8
19
 
9
20
  ```
10
21
  {
@@ -23,14 +34,22 @@ Use by importing:
23
34
  },
24
35
  math: {
25
36
  numbers: [functions]
37
+ },
38
+ exceptions: {
39
+ BadlyInitializedError
26
40
  }
27
41
  }
28
42
  ```
29
43
 
30
- You can also import individual functions like the following snippet:
44
+ Individual functions can be destructured out of that as well, though it nests
45
+ three levels deep:
31
46
 
32
47
  `const { lang: { checks : { isNil } } } = require('@wizhut_tech/wizjs');`
33
48
 
49
+ Both forms hand back the same objects, so they mix freely. Library internals
50
+ (under `src/internal/`) are deliberately not reachable as subpaths --
51
+ `BadlyInitializedError` is re-exported from the root import instead.
52
+
34
53
  ### I/O
35
54
 
36
55
  * **Files** utility functions ... [[docs](docs/io_files.md)]
package/src/io/files.js CHANGED
@@ -3,7 +3,7 @@ const fs = require('fs').promises;
3
3
 
4
4
  async function loadFully(filename) {
5
5
  try {
6
- const data = await fs.readFile(filename, 'utf8');
6
+ return await fs.readFile(filename, 'utf8');
7
7
  } catch (err) {
8
8
  return null;
9
9
  }
package/CLAUDE.md DELETED
@@ -1,56 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## What this is
6
-
7
- `@wizhut_tech/wizjs` is a small, curated JavaScript utility library — Python idioms and selected stdlib helpers (`itertools`, `functools`, `collections`) for everyday JS, explicitly *not* trying to be lodash. Helpers are grouped into top-level namespaces (`io`, `lang`, `math`) with focused sub-areas under each. It is plain JavaScript (no TypeScript), CommonJS (no `type: "module"`, no build step), and ships its source directly to npm. It pulls in no runtime dependencies; the only third-party use is Node's built-in `fs` in `io/files`, so anything outside that namespace is runtime-agnostic.
8
-
9
- ## Commands
10
-
11
- ```sh
12
- npm install # install devDependencies (only `tap`)
13
- npm test # full suite — runs `tap run` over __tests__/
14
- npx tap __tests__/lang_arrays.test.js # run a SINGLE test file
15
- ```
16
-
17
- There is **no build, lint, or typecheck step** — `test` is the only npm script. The test runner is [`tap`](https://node-tap.org) (v21). Tests live in `__tests__/`, one file per namespace sub-area, named `<namespace>_<area>.test.js` (e.g. `lang_arrays.test.js`, `math_numbers.test.js`).
18
-
19
- Publishing goes to the scoped package `@wizhut_tech/wizjs` and is tag-driven, from CI: `npm version patch` then `git push --follow-tags`, and the `v<x.y.z>` tag triggers [`.github/workflows/release.yml`](.github/workflows/release.yml) — tag/`package.json` check, `npm ci && npm test`, `npm publish --access public`, GitHub release. Authentication is npm OIDC trusted publishing, so there is no token anywhere; don't publish from a laptop, as hand-published versions lose the provenance attestation. There is no `files` allowlist or `.npmignore`, so the published tarball includes `index.js` + `src/`. The repo declares the MIT license in `package.json` but currently has **no LICENSE file**.
20
-
21
- ## Architecture
22
-
23
- The single entry point is `index.js`. It `require`s each leaf module from `src/` and composes them into one nested object — the entire public API surface:
24
-
25
- ```
26
- io.files → src/io/files.js (loadFully)
27
- lang.arrays → src/lang/arrays.js (compact, accumulate, Operator, unique, chunk, flatten)
28
- lang.singleton → src/lang/singleton.js (singleton, getInstance)
29
- lang.flow → src/lang/flow.js (if_exception)
30
- lang.checks → src/lang/checks.js (isNil, isArray, isObject, isString, isNumber, isBoolean, isFunction, isInteger, isEmpty)
31
- lang.functools → src/lang/functools.js (reflexive, partial, compose, once, memoize, cache, reduce)
32
- lang.itertools → src/lang/itertools.js (count, repeat, cycle, chain, take, zip, range, enumerate, islice, takewhile, dropwhile, filterfalse, zip_longest, product, groupby, starmap, batched, pairwise, compress)
33
- lang.objects → src/lang/objects.js (pick, omit, get)
34
- lang.collections → src/lang/collections.js (Counter, DefaultDict)
35
- math.numbers → src/math/numbers.js (toInteger, toNumber, clamp, mod)
36
- exceptions → src/internal/exceptions.js (BadlyInitializedError)
37
- ```
38
-
39
- Each leaf module is a flat `module.exports = { ... }` of functions (plus the occasional class, e.g. `Operator`). The namespace objects in `index.js` are just references to those `module.exports`, so whatever a leaf exports is reachable both as `wizjs.lang.arrays.<name>` and via destructuring (`const { lang: { arrays: { compact } } } = require('@wizhut_tech/wizjs')`).
40
-
41
- Directory layout mirrors the namespace path exactly: `src/<top-namespace>/<area>.js`. `src/internal/` holds non-public building blocks (currently just `exceptions.js`); its contents are surfaced selectively — `index.js` re-exports `BadlyInitializedError` under the top-level `exceptions` key rather than mounting the whole module.
42
-
43
- Where a helper belongs:
44
- - I/O / filesystem / external resources → `src/io/`
45
- - General language utilities → `src/lang/` (`checks` = type/predicate tests, `arrays` = array ops, `flow` = control-flow wrappers, `functools` = function helpers, `itertools` = lazy generators, `objects` = plain-object pick/omit/get, `collections` = Python `Counter` / `DefaultDict`, `singleton` = the singleton hack)
46
- - Numeric helpers → `src/math/`
47
- - Shared internals not meant as public API → `src/internal/`
48
-
49
- Cross-module reuse is direct relative `require` (e.g. `arrays.js` and `numbers.js` both import `isNil` from `lang/checks.js`; `itertools.js` imports `toInteger` from `math/numbers.js`).
50
-
51
- ## Conventions
52
-
53
- - **Module format:** CommonJS only — `require(...)` / `module.exports`. No ESM, no transpilation.
54
- - **Naming:** functions are camelCase (`isNil`, `loadFully`, `toInteger`); Python stdlib names are kept as in Python (`if_exception`, `takewhile`, `zip_longest`, `groupby`). Classes are PascalCase (`Operator`, `BadlyInitializedError`, `Counter`, `DefaultDict`). Lazy/iterator helpers in `itertools` are generator functions.
55
- - **Adding a helper to an existing area:** write the function in the matching `src/<ns>/<area>.js`, add it to that file's `module.exports`, and add cases to the corresponding `__tests__/<ns>_<area>.test.js`. It becomes available automatically since `index.js` references the whole `module.exports`.
56
- - **Adding a new area or namespace:** create `src/<ns>/<area>.js`, then `require` it in `index.js` and mount it under the right namespace object. Add a `docs/<ns>_<area>.md` page and a matching `__tests__/<ns>_<area>.test.js`. Each namespace sub-area has a doc page under `docs/` linked from `readme.md` (lowercase filename).