bend-schema 0.1.0 → 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 +69 -1
- package/package.json +11 -2
- package/src/effect.ts +31 -0
package/README.md
CHANGED
|
@@ -16,6 +16,12 @@ package is `bend-schema-lib`.
|
|
|
16
16
|
bun add bend-schema
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
From a Bend file there is no install step: import the checker from the hub.
|
|
20
|
+
|
|
21
|
+
```bend
|
|
22
|
+
import bend-schema-lib@0.1.0.0/core.bend as S
|
|
23
|
+
```
|
|
24
|
+
|
|
19
25
|
Use it with [Bun](https://bun.sh). The package ships TypeScript source, and
|
|
20
26
|
Node.js refuses to run TypeScript from `node_modules`. You do not need Bend
|
|
21
27
|
installed.
|
|
@@ -74,6 +80,68 @@ See **[docs/schemas.md](https://github.com/nohzafk/bend-schema/blob/main/docs/sc
|
|
|
74
80
|
for how to write schemas:
|
|
75
81
|
objects, unions, custom rules, error messages and encoding.
|
|
76
82
|
|
|
83
|
+
## Optional Effect v4 adapter
|
|
84
|
+
|
|
85
|
+
Convert an existing schema into an Effect codec (a schema with decoding and
|
|
86
|
+
encoding). The adapter is a separate entry point; importing `bend-schema`
|
|
87
|
+
does not load or require Effect.
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
bun add bend-schema effect@4.0.1
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { s } from "bend-schema";
|
|
95
|
+
import { toEffect } from "bend-schema/effect";
|
|
96
|
+
import { Schema } from "effect";
|
|
97
|
+
import { Rpc } from "effect/rpc";
|
|
98
|
+
|
|
99
|
+
const Message = toEffect(s.object({ text: s.str(), note: s.str().optional() }));
|
|
100
|
+
const decoded = Schema.decodeUnknownSync(Message)({ text: "hello", extra: true });
|
|
101
|
+
// decoded is inferred as { text: string; note?: string }; extra is dropped.
|
|
102
|
+
const encoded = Schema.encodeSync(Message)(decoded);
|
|
103
|
+
|
|
104
|
+
const Echo = Rpc.make("Echo", { payload: Message, success: Message });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The API infers `T` from the input schema; no type assertion is needed:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
function toEffect<T>(schema: Schema<T>): EffectSchema.decodeTo<
|
|
111
|
+
EffectSchema.declareConstructor<T, T, readonly []>, typeof EffectSchema.Unknown
|
|
112
|
+
>;
|
|
113
|
+
// Schema is bend-schema's type; EffectSchema is effect's Schema module.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The concrete return type keeps Effect's constructor input (`~type.make.in`)
|
|
117
|
+
as `T`. RPC clients therefore accept the inferred object, tagged union, or
|
|
118
|
+
string payload and reject incorrectly typed fields at compile time. Widening
|
|
119
|
+
the adapter to `EffectSchema.Codec<T, unknown>` erases that constructor input
|
|
120
|
+
to `unknown`; keep the inferred return type when passing it to `Rpc.make`.
|
|
121
|
+
TypeScript checks field types, not refinement predicates or numeric ranges;
|
|
122
|
+
those are still validated at runtime.
|
|
123
|
+
|
|
124
|
+
Decoding returns `schema.parse(input).value`, not the untouched input.
|
|
125
|
+
Encoding validates the decoded value with `parse`, then calls `schema.encode`.
|
|
126
|
+
Both directions are synchronous and require no Effect services.
|
|
127
|
+
|
|
128
|
+
- **Errors:** the first parse error keeps its path and reason in an Effect
|
|
129
|
+
`SchemaIssue.Pointer` and `InvalidValue`. Effect adds surrounding field paths
|
|
130
|
+
when the codec is nested. The bend-schema `proved` flag is not carried over.
|
|
131
|
+
- **Semantics:** unknown keys are dropped unless the object is strict. Optional
|
|
132
|
+
fields, tagged unions, refinements and the existing size limits still apply.
|
|
133
|
+
Effect parse options do not replace bend-schema's validation rules.
|
|
134
|
+
- **Representation:** the encoded type is `unknown`, as with `schema.encode`.
|
|
135
|
+
The Effect AST uses an opaque declaration, not a generated structural schema;
|
|
136
|
+
it does not expose the Bend shape for JSON Schema generation. No Bend datatype
|
|
137
|
+
parser or source generator is involved.
|
|
138
|
+
- **Failures:** thrown schema configuration errors and thrown refinement
|
|
139
|
+
callbacks remain programming errors, not validation issues. Exceptions from
|
|
140
|
+
`encode` become Effect validation issues with the exception's message.
|
|
141
|
+
|
|
142
|
+
Effect is an optional peer dependency (`^4.0.1`); tests pin `4.0.1`.
|
|
143
|
+
Effect v3 and v4 prereleases are not supported by this adapter.
|
|
144
|
+
|
|
77
145
|
## Examples
|
|
78
146
|
|
|
79
147
|
Each example is a small runnable project with tests:
|
|
@@ -155,7 +223,7 @@ The test suite proves it.
|
|
|
155
223
|
[bend-emit](https://github.com/nohzafk/bend-emit):
|
|
156
224
|
|
|
157
225
|
```sh
|
|
158
|
-
bun add -d
|
|
226
|
+
bun add -d bend-emit
|
|
159
227
|
bunx bend-emit core.bend dist # writes dist/core.mjs and dist/core.d.mts
|
|
160
228
|
```
|
|
161
229
|
|
package/package.json
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bend-schema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"devDependencies": {
|
|
7
7
|
"@types/bun": "^1.2.0",
|
|
8
8
|
"bend-emit": "0.3.1",
|
|
9
9
|
"bend-falsify": "github:nohzafk/bend-falsify#a50873f",
|
|
10
|
+
"effect": "4.0.1",
|
|
10
11
|
"typescript": "^7.0.2"
|
|
11
12
|
},
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"effect": "^4.0.1"
|
|
15
|
+
},
|
|
16
|
+
"peerDependenciesMeta": {
|
|
17
|
+
"effect": { "optional": true }
|
|
18
|
+
},
|
|
12
19
|
"exports": {
|
|
13
20
|
".": "./src/index.ts",
|
|
14
21
|
"./codec": "./src/codec.ts",
|
|
15
|
-
"./gen": "./src/gen.ts"
|
|
22
|
+
"./gen": "./src/gen.ts",
|
|
23
|
+
"./effect": "./src/effect.ts"
|
|
16
24
|
},
|
|
17
25
|
"bin": {
|
|
18
26
|
"bend-schema": "./src/gen-cli.ts"
|
|
@@ -28,6 +36,7 @@
|
|
|
28
36
|
"files": [
|
|
29
37
|
"src/index.ts",
|
|
30
38
|
"src/codec.ts",
|
|
39
|
+
"src/effect.ts",
|
|
31
40
|
"src/gen.ts",
|
|
32
41
|
"src/gen-cli.ts",
|
|
33
42
|
"core/core.bend",
|
package/src/effect.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Optional Effect v4 bridge. The root entry does not import Effect.
|
|
2
|
+
import { Effect, Schema as S, SchemaGetter, SchemaIssue } from "effect";
|
|
3
|
+
import type { Schema } from "./index";
|
|
4
|
+
|
|
5
|
+
/** Decode with bend-schema's parse (including transformations), and encode
|
|
6
|
+
* with its encode. The encoded representation is unknown; T is inferred.
|
|
7
|
+
* Keep the concrete schema type: Codec<T, unknown> erases RPC's make input. */
|
|
8
|
+
export function toEffect<T>(schema: Schema<T>): S.decodeTo<
|
|
9
|
+
S.declareConstructor<T, T, readonly []>, typeof S.Unknown
|
|
10
|
+
> {
|
|
11
|
+
const value = S.declareConstructor<T>()([], () => (input) => {
|
|
12
|
+
const result = schema.parse(input);
|
|
13
|
+
return result.ok
|
|
14
|
+
? Effect.succeed(result.value)
|
|
15
|
+
: Effect.fail(new SchemaIssue.Pointer(
|
|
16
|
+
result.error.path,
|
|
17
|
+
new SchemaIssue.InvalidValue({ message: result.error.message }),
|
|
18
|
+
));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return S.Unknown.pipe(S.decodeTo(value, {
|
|
22
|
+
// The declaration validates this unknown value and returns parse's T.
|
|
23
|
+
decode: SchemaGetter.transform((input: unknown) => input as T),
|
|
24
|
+
encode: SchemaGetter.transformEffect((input: T) => Effect.try({
|
|
25
|
+
try: () => schema.encode(input),
|
|
26
|
+
catch: (error) => new SchemaIssue.InvalidValue({
|
|
27
|
+
message: error instanceof Error ? error.message : String(error),
|
|
28
|
+
}),
|
|
29
|
+
})),
|
|
30
|
+
}));
|
|
31
|
+
}
|