@sandlada/result 0.0.1-20260701.a → 0.0.1-20260701.b
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 +89 -152
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,183 +1,120 @@
|
|
|
1
|
-
# @sandlada/
|
|
1
|
+
# @sandlada/result
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
- **use 系列** — 產生 `var()` 引用(`var(--name, fallback)`),用於組合進其他宣告中
|
|
7
|
+
`@sandlada/result` is a TypeScript library implementing the **Result pattern** — a type-safe, exception-free approach to error handling. It makes error flows explicit in the type system so you never wonder whether a function can fail.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
Unlike traditional Result libraries that hardcode a single error type, `@sandlada/result` is **fully generic**: you bring your own error shapes (discriminated unions, classes, or plain objects).
|
|
9
10
|
|
|
10
|
-
##
|
|
11
|
+
## :zap: Highlights
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
專案使用 TypeScript 6 + ESM,匯入即可獲得完整的型別推斷。
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## define 系列 — 產生 CSS 宣告
|
|
21
|
-
|
|
22
|
-
| 函式 | 說明 | 文件 |
|
|
23
|
-
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
24
|
-
| `defineVars(name, value)` | 基本變數定義,回傳字串陣列 | [docs/define-vars.md](./docs/define-vars.md) |
|
|
25
|
-
| `defineLogicalBorderRadiusVars(base, value, options?)` | 展開為四個邏輯角(`start-start` / `start-end` / `end-start` / `end-end`),回傳陣列,支援 `{ semi?, prefix? }` | [docs/define-logical-border-radius-vars.md](./docs/define-logical-border-radius-vars.md) |
|
|
26
|
-
| `defineLogicalBorderRadiusVarsRecord(base, value)` | 同上,但回傳 Record 物件 | [docs/define-logical-border-radius-vars-record.md](./docs/define-logical-border-radius-vars-record.md) |
|
|
27
|
-
| `defineTokenRefsRecord(tokens, options?)` | 將設計 Token 綁定為內部變數(`--_key: var(--key, value)`),支援形狀屬性展開與 `{ prefix? }` | [docs/define-token-refs-record.md](./docs/define-token-refs-record.md) |
|
|
28
|
-
| `defineOverrides(source, overrides)(prefix?)` | 型別安全的樣式覆蓋輔助,Curried API,可傳入 `null` 跳過型別約束 | [docs/define-overrides.md](./docs/define-overrides.md) |
|
|
29
|
-
|
|
30
|
-
```ts
|
|
31
|
-
import { defineVars } from '@sandlada/jss'
|
|
32
|
-
|
|
33
|
-
// ['--color: red']
|
|
34
|
-
defineVars('color', 'red')
|
|
13
|
+
- Fully generic `TError` — define your own error types
|
|
14
|
+
- Zero dependencies
|
|
15
|
+
- ESM-only, strict TypeScript
|
|
16
|
+
- Inspired by the C# Result pattern
|
|
35
17
|
|
|
36
|
-
|
|
37
|
-
defineVars({ color: 'red', 'bg-color': 'blue' })
|
|
18
|
+
## :eyes: Installation
|
|
38
19
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
import { defineTokenRefsRecord } from '@sandlada/jss'
|
|
45
|
-
|
|
46
|
-
const AppTokens = {
|
|
47
|
-
'button-text-color': 'red',
|
|
48
|
-
'button-bg-color': 'white',
|
|
49
|
-
'button-shape': 'var(--md-sys-shape-corner-full, 9999px)',
|
|
50
|
-
} as const
|
|
51
|
-
|
|
52
|
-
// 將設計 Token 綁定為內部變數:
|
|
53
|
-
// { '--_button-text-color': 'var(--button-text-color, red)', ... }
|
|
54
|
-
defineTokenRefsRecord(AppTokens)
|
|
55
|
-
|
|
56
|
-
// 使用 prefix 為所有 token 加上前綴:
|
|
57
|
-
defineTokenRefsRecord(AppTokens, { prefix: '--md-badge' })
|
|
58
|
-
// { '--_button-text-color': 'var(--md-badge-button-text-color, red)', ... }
|
|
59
|
-
|
|
60
|
-
// 展開形狀屬性為四個邏輯角:
|
|
61
|
-
defineTokenRefsRecord(AppTokens, { expandShapes: ['button-shape'] })
|
|
62
|
-
// {
|
|
63
|
-
// '--_button-text-color': 'var(--button-text-color, red)',
|
|
64
|
-
// '--_button-shape-start-start': 'var(--button-shape-start-start, var(--md-sys-shape-corner-full, 9999px))',
|
|
65
|
-
// '--_button-shape-start-end': 'var(--button-shape-start-end, ...)',
|
|
66
|
-
// '--_button-shape-end-start': 'var(--button-shape-end-start, ...)',
|
|
67
|
-
// '--_button-shape-end-end': 'var(--button-shape-end-end, ...)',
|
|
68
|
-
// }
|
|
69
|
-
|
|
70
|
-
// 加入基底變數作為中繼備援(兩層 var() 遞迴):
|
|
71
|
-
defineTokenRefsRecord(AppTokens, { expandShapes: ['button-shape'], useBaseFallback: true })
|
|
72
|
-
// {
|
|
73
|
-
// '--_button-shape-start-start': 'var(--button-shape-start-start, var(--button-shape, var(--md-sys-shape-corner-full, 9999px)))',
|
|
74
|
-
// ...
|
|
75
|
-
// }
|
|
20
|
+
```bash
|
|
21
|
+
npm i @sandlada/result
|
|
76
22
|
```
|
|
77
23
|
|
|
78
|
-
|
|
24
|
+
> **ESM only.** This package cannot be used with `require()`. Your project must use ESM (`import`) or dynamic `import()`.
|
|
79
25
|
|
|
80
|
-
##
|
|
26
|
+
## :ship: Quick Start
|
|
81
27
|
|
|
82
28
|
```ts
|
|
83
|
-
import {
|
|
29
|
+
import { Result, type IResultOfT } from '@sandlada/result';
|
|
30
|
+
|
|
31
|
+
// Define your error type (discriminated union recommended)
|
|
32
|
+
type AppError =
|
|
33
|
+
| { kind: 'NotFound'; id: string }
|
|
34
|
+
| { kind: 'Validation'; fields: Record<string, string> };
|
|
35
|
+
|
|
36
|
+
function getUser(id: string): IResultOfT<User, AppError> {
|
|
37
|
+
if (!id) {
|
|
38
|
+
return Result.Failure<User, AppError>({
|
|
39
|
+
kind: 'Validation',
|
|
40
|
+
fields: { id: 'Required' },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const user = db.find(id);
|
|
44
|
+
if (!user) {
|
|
45
|
+
return Result.Failure<User, AppError>({
|
|
46
|
+
kind: 'NotFound',
|
|
47
|
+
id,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return Result.Success(user);
|
|
51
|
+
}
|
|
84
52
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
53
|
+
// Consume with type narrowing
|
|
54
|
+
const result = getUser('42');
|
|
55
|
+
if (result.isSuccess) {
|
|
56
|
+
console.log(result.value.name); // ✅ User
|
|
57
|
+
} else {
|
|
58
|
+
switch (result.error.kind) {
|
|
59
|
+
case 'NotFound': /* ... */ break;
|
|
60
|
+
case 'Validation': /* ... */ break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
89
64
|
|
|
90
|
-
|
|
91
|
-
// { 'outline-color': 'blue' }
|
|
92
|
-
defineOverrides(FocusRing, { 'outline-color': 'blue' })()
|
|
65
|
+
## :ledger: Core Types
|
|
93
66
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
67
|
+
| Export | Kind | Signature | Description |
|
|
68
|
+
| ------------ | --------- | ------------------------------------ | -------------------------------- |
|
|
69
|
+
| `IResult` | interface | `IResult<TError = Error>` | Base result contract (no value) |
|
|
70
|
+
| `IResultOfT` | interface | `IResultOfT<TValue, TError = Error>` | Result carrying a success value |
|
|
71
|
+
| `Result` | class | `Result<TError = Error>` | Base class with static factories |
|
|
72
|
+
| `ResultOfT` | class | `ResultOfT<TValue, TError = Error>` | Generic result class with value |
|
|
97
73
|
|
|
98
|
-
|
|
99
|
-
defineOverrides(FocusRing, {})()
|
|
74
|
+
### Factory Methods
|
|
100
75
|
|
|
101
|
-
|
|
102
|
-
defineOverrides(null, { 'outline-color': 'blue' })()
|
|
76
|
+
All factories live on `Result`:
|
|
103
77
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
```
|
|
78
|
+
| Method | Returns |
|
|
79
|
+
| ----------------------------- | ----------------------------------------------------------- |
|
|
80
|
+
| `Result.Success()` | `IResult` — void success |
|
|
81
|
+
| `Result.Success(value)` | `IResultOfT<TValue>` — success with value (T inferred) |
|
|
82
|
+
| `Result.Failure(error)` | `IResult` — void failure (Error only) |
|
|
83
|
+
| `Result.Failure<T, E>(error)` | `IResultOfT<T, E>` — typed failure (T explicit, E inferred) |
|
|
111
84
|
|
|
112
|
-
|
|
85
|
+
## :package: Integration Pattern
|
|
113
86
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
部分函式支援第二個(或第三個)參數傳入 `JSSOptions` 物件來控制行為:
|
|
87
|
+
Bind your error type once and eliminate generic boilerplate:
|
|
117
88
|
|
|
118
89
|
```ts
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
90
|
+
// app-result.ts
|
|
91
|
+
import { Result } from '@sandlada/result';
|
|
92
|
+
import type { IResultOfT } from '@sandlada/result';
|
|
93
|
+
import type { AppError } from './errors.js';
|
|
94
|
+
|
|
95
|
+
export type AppResult<T = void> = IResultOfT<T, AppError>;
|
|
96
|
+
|
|
97
|
+
export const AppResult = {
|
|
98
|
+
Success(): AppResult<void> { return Result.Success() as AppResult<void>; },
|
|
99
|
+
Success<T>(value: T): AppResult<T> { return Result.Success(value) as AppResult<T>; },
|
|
100
|
+
Failure(error: AppError): AppResult<never> { return Result.Failure<never, AppError>(error); },
|
|
101
|
+
} as const;
|
|
123
102
|
```
|
|
124
103
|
|
|
125
|
-
**`semi`** — 加上分號尾綴,適用於需要 inline style 語句的場景。
|
|
126
|
-
|
|
127
|
-
**`prefix`** — 為產生的 CSS 變數名稱加上自訂前綴。例如使用 `{ prefix: '--md-badge' }` 時,
|
|
128
|
-
`useVars('color-primary', 'blue', { prefix: '--md-badge' })` 會輸出 `var(--md-badge-color-primary, blue)`,
|
|
129
|
-
取代預設的 `var(--color-primary, blue)`。
|
|
130
|
-
|
|
131
|
-
> **注意:** `prefix` 僅影響 CSS 變數的**名稱部分**,不影響 Record 的 JavaScript 鍵名。
|
|
132
|
-
> 支援 prefix 的函式:`useVars`、`useVarsRecord`、`useInternalVarsRecord`、`useLogicalBorderRadiusVars`、
|
|
133
|
-
> `useLogicalBorderRadiusVarsRecord`、`defineLogicalBorderRadiusVars`、`defineTokenRefsRecord`。
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
## use 系列 — 產生 `var()` 引用
|
|
138
|
-
|
|
139
|
-
| 函式 | 說明 | 文件 |
|
|
140
|
-
| -------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
141
|
-
| `useVars(name, fallback, options?)` | 基本變數引用,回傳陣列,支援 `{ semi?, prefix? }` | [docs/use-vars.md](./docs/use-vars.md) |
|
|
142
|
-
| `useVarsRecord(name, fallback, options?)` | 基本變數引用,回傳 Record,支援 `{ semi?, prefix? }` | [docs/use-vars-record.md](./docs/use-vars-record.md) |
|
|
143
|
-
| `useInternalVars(name, fallback)` | 內部變數(`--_` 前綴)引用,回傳陣列 | [docs/use-internal-vars.md](./docs/use-internal-vars.md) |
|
|
144
|
-
| `useInternalVarsRecord(name, fallback, options?)` | 內部變數引用,回傳 Record,支援 `{ semi?, prefix? }` | [docs/use-internal-vars-record.md](./docs/use-internal-vars-record.md) |
|
|
145
|
-
| `useLogicalBorderRadiusVars(corner, fallback, options?)` | 邏輯角變數(兩層 `var()` 遞迴),回傳陣列,支援 `{ semi?, prefix? }` | [docs/use-logical-border-radius-vars.md](./docs/use-logical-border-radius-vars.md) |
|
|
146
|
-
| `useLogicalBorderRadiusVarsRecord(corner, fallback, options?)` | 邏輯角變數,回傳 Record,支援 `{ semi?, prefix? }` | [docs/use-logical-border-radius-vars-record.md](./docs/use-logical-border-radius-vars-record.md) |
|
|
147
|
-
|
|
148
104
|
```ts
|
|
149
|
-
|
|
105
|
+
// usage — no TError generic anywhere
|
|
106
|
+
import { AppResult } from './app-result.js';
|
|
150
107
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// ['var(--a, var(--b, var(--c, default-value)))']
|
|
156
|
-
useVars(['a', 'b', 'c'], 'default-value')
|
|
157
|
-
|
|
158
|
-
// Record 模式自動剝除 -- 前綴作為鍵名:
|
|
159
|
-
// { 'color-primary': 'var(--color-primary, blue)' }
|
|
160
|
-
useVarsRecord('--color-primary', 'blue')
|
|
161
|
-
|
|
162
|
-
// 使用 prefix 選項為所有變數名稱加上前綴:
|
|
163
|
-
// ['var(--md-badge-color-primary, blue)']
|
|
164
|
-
useVars('color-primary', 'blue', { prefix: '--md-badge' })
|
|
165
|
-
|
|
166
|
-
// ['var(--md-badge-a, var(--md-badge-b, var(--md-badge-c, default-value)))']
|
|
167
|
-
useVars(['a', 'b', 'c'], 'default-value', { prefix: '--md-badge' })
|
|
108
|
+
function getUser(id: string): AppResult<User> {
|
|
109
|
+
if (!id) return AppResult.Failure({ kind: 'Validation', fields: { id: 'Required' } });
|
|
110
|
+
return AppResult.Success({ id, name: 'Alice' });
|
|
111
|
+
}
|
|
168
112
|
```
|
|
169
113
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
## 開發
|
|
114
|
+
## :ledger: Further Reading
|
|
173
115
|
|
|
174
|
-
|
|
175
|
-
# 安裝依賴
|
|
176
|
-
npm install
|
|
116
|
+
For detailed documentation — railway-oriented programming, multi-layer error mapping, result aggregation, and C# comparison — see [SPEC.md](./SPEC.md).
|
|
177
117
|
|
|
178
|
-
|
|
179
|
-
npx vitest
|
|
118
|
+
## License
|
|
180
119
|
|
|
181
|
-
|
|
182
|
-
npx tsc --noEmit
|
|
183
|
-
```
|
|
120
|
+
MIT
|
package/package.json
CHANGED