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.
Files changed (3) hide show
  1. package/README.md +69 -1
  2. package/package.json +11 -2
  3. 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 github:nohzafk/bend-emit
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.1.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
+ }