bimorph 0.0.1 → 0.0.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/LICENSE +21 -0
- package/README.md +69 -39
- package/package.json +2 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oleksandr Zhuravlov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,57 +1,87 @@
|
|
|
1
1
|
# bimorph
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/bimorph)
|
|
4
|
+
[](LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
**Bidirectional data mapping for TypeScript.** Define a mapping once and get both
|
|
7
|
+
directions — `decode` (wire → domain) and `encode` (domain → wire) — with the reverses
|
|
8
|
+
that *don't* invert cleanly surfaced in the type instead of failing silently.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
transforms, missing keys, read-only fields, and reverses that need runtime context
|
|
12
|
-
the value doesn't carry. bimorph's thesis: *embrace that reality, surface it early,
|
|
13
|
-
and give the caller explicit, legible escape hatches* — rather than pretend every
|
|
14
|
-
mapping is a clean isomorphism.
|
|
10
|
+
```ts
|
|
11
|
+
import { Enum } from 'bimorph';
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
const Status = Enum([
|
|
14
|
+
[1, 'active'],
|
|
15
|
+
[0, 'inactive'],
|
|
16
|
+
]);
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Status.decode(1); // 'active' — wire → domain
|
|
19
|
+
Status.encode('active'); // 1 — domain → wire
|
|
20
|
+
```
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
One declaration, both directions — no hand-written reverse lookup drifting out of sync.
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
## The problem it kills
|
|
25
|
+
|
|
26
|
+
The happy path is easy; a lookup object already does that. The pain is the *reverse*:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const STATUSES = { 0: 'BAD', 1: 'OK' };
|
|
30
|
+
const label = STATUSES[0]; // easy
|
|
31
|
+
const value = Object.entries(STATUSES).find(([, v]) => v === 'OK')?.[0]; // ugh — and it drifts
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
And most real mappings *don't* invert cleanly: duplicate values, lossy transforms,
|
|
35
|
+
missing keys, read-only fields, reverses that need runtime context the value doesn't
|
|
36
|
+
carry. Most tools ignore that or hide it. bimorph's thesis: **surface non-invertibility
|
|
37
|
+
in the type, and give explicit escape hatches** — instead of pretending every mapping is
|
|
38
|
+
a clean isomorphism.
|
|
39
|
+
|
|
40
|
+
That shows up as:
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
- **Fidelity tiers** — `iso` (exact round-trip), `lossy` (no round-trip promised),
|
|
43
|
+
`partial` (the inverse can fail). A `partial` codec doesn't even offer a throwing
|
|
44
|
+
`decode`; the type forces you through `safeDecode`.
|
|
45
|
+
- **Doors, not flags** — `decode` throws with a path, `safeDecode` returns a `Result`,
|
|
46
|
+
`decodeOr` falls back, `validate` accumulates every error. The behavior is visible at
|
|
47
|
+
the call site, never a hidden config flag.
|
|
48
|
+
- **Aliases** — several wire values decode to one domain value, but `encode` only ever
|
|
49
|
+
emits the canonical one (narrowed in the type), so a migration can't leak a legacy
|
|
50
|
+
spelling.
|
|
51
|
+
- **Context** — a codec that needs a `region` or `locale` says so in its type; you can't
|
|
52
|
+
silently forget to pass it.
|
|
30
53
|
|
|
31
|
-
|
|
32
|
-
`partial` / `Enum` (with aliases) / `Struct` / `Field` / `bind`.
|
|
54
|
+
## Install
|
|
33
55
|
|
|
34
56
|
```bash
|
|
35
|
-
npm install
|
|
36
|
-
npm run typecheck # tsc --noEmit over src/
|
|
37
|
-
npm run check:runtime # runtime-behaviour regression gate over src/
|
|
38
|
-
npm run docs # run the documentation site (apps/docs) at http://localhost:3411
|
|
57
|
+
npm install bimorph
|
|
39
58
|
```
|
|
40
59
|
|
|
41
|
-
|
|
60
|
+
> **Early release — expect breaking changes before 1.0.** The API has just stabilized,
|
|
61
|
+
> but the package currently ships TypeScript source (`src/index.ts`): it works in a
|
|
62
|
+
> TypeScript project with a bundler, and a compiled `dist/` (`.js` + `.d.ts`) for plain
|
|
63
|
+
> Node/JS is on the way. Pin the version.
|
|
42
64
|
|
|
43
|
-
|
|
44
|
-
`encode` returns the *narrow* canonical-only union. A migration cannot emit a legacy spelling.
|
|
45
|
-
2. **Contextual codecs** — a `Ctx` third param whose trailing argument is required
|
|
46
|
-
(`decode(b)` without it is a type error), `.bind(ctx)` erases it back to a plain codec,
|
|
47
|
-
and `Struct` **intersects** the contexts of its fields into one merged bag.
|
|
48
|
-
3. **`Partial` removes the throwing decode door** at the type level — you're forced to `safeDecode`.
|
|
65
|
+
## Documentation
|
|
49
66
|
|
|
50
|
-
|
|
67
|
+
The full documentation site — Fumadocs, with every `ts twoslash` example type-checked
|
|
68
|
+
against the real types at build time — lives in [`apps/docs/`](apps/docs). Run it with
|
|
69
|
+
`npm run docs` and open http://localhost:3411.
|
|
51
70
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
Design record:
|
|
72
|
+
|
|
73
|
+
- [`docs/DESIGN.md`](docs/DESIGN.md) — principles, the codec contract, and the API surface.
|
|
74
|
+
- [`docs/SCENARIOS.md`](docs/SCENARIOS.md) — ~28 real-world mappings the design was tested against.
|
|
75
|
+
- [`docs/DOGFOOD.md`](docs/DOGFOOD.md) — the design written against those scenarios, and the gaps it exposed.
|
|
76
|
+
|
|
77
|
+
## Development
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm run typecheck # tsc --noEmit over src/
|
|
81
|
+
npm run check:runtime # runtime-behaviour regression gate
|
|
82
|
+
npm run docs # the documentation site at http://localhost:3411
|
|
57
83
|
```
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
[MIT](LICENSE) © 2026 Oleksandr Zhuravlov
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bimorph",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"description": "Bidirectional data mapping for TypeScript — define a mapping once, get decode and encode, with non-invertibility surfaced in the type.",
|
|
6
7
|
"exports": {
|
|
7
8
|
".": "./src/index.ts"
|