Skip to content

Commit cd21677

Browse files
committed
feature: add support for effect as a validator WIP
1 parent 41e3d62 commit cd21677

File tree

20 files changed

+1108
-19
lines changed

20 files changed

+1108
-19
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
"tangrams": minor
3+
---
4+
5+
Add Effect Schema as a fourth validation library option alongside Zod, Valibot, and ArkType.
6+
7+
Effect Schema is part of the Effect ecosystem and uses a functional approach to schema definition. Unlike the other validators which natively implement Standard Schema, Effect Schema requires wrapping with `Schema.standardSchemaV1()` for use with TanStack Form - this wrapping is handled automatically in generated form options.
8+
9+
To use Effect Schema:
10+
11+
```typescript
12+
import { defineConfig } from "tangrams"
13+
14+
export default defineConfig({
15+
validator: "effect",
16+
sources: [/* ... */],
17+
})
18+
```
19+
20+
Custom scalar mappings for Effect use the `Schema.` prefix:
21+
22+
```typescript
23+
overrides: {
24+
scalars: {
25+
DateTime: "Schema.String",
26+
JSON: "Schema.Unknown",
27+
},
28+
}
29+
```

apps/docs/content/docs/index.mdx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -233,20 +233,23 @@ export default defineConfig({
233233
| Option | Type | Default | Description |
234234
|--------|------|---------|-------------|
235235
| `output` | `string` | `"."` | Directory where the `tangrams` folder will be generated |
236-
| `validator` | `"zod" \| "valibot" \| "arktype"` | `"zod"` | Validation library for generated schemas |
236+
| `validator` | `"zod" \| "valibot" \| "arktype" \| "effect"` | `"zod"` | Validation library for generated schemas |
237237
| `sources` | `SourceConfig[]` | (required) | Array of data sources (minimum 1 required) |
238238

239239
### Validator Libraries
240240

241-
Tangrams supports three validation libraries that all implement [Standard Schema](https://github.com/standard-schema/standard-schema):
241+
Tangrams supports four validation libraries that all implement [Standard Schema](https://github.com/standard-schema/standard-schema):
242242

243243
| Library | Import | Description |
244244
|---------|--------|-------------|
245245
| **Zod** | `zod` | The default. Full-featured schema validation with great TypeScript inference. |
246246
| **Valibot** | `valibot` | Lightweight alternative with modular design and smaller bundle size. |
247247
| **ArkType** | `arktype` | Type-first validation with runtime safety and excellent TypeScript integration. |
248+
| **Effect** | `effect` | Part of the Effect ecosystem. Requires `Schema.standardSchemaV1()` wrapper for TanStack Form. |
248249

249-
All three libraries work seamlessly with TanStack Form since they implement the Standard Schema protocol. Choose based on your preferences for bundle size, API style, or existing usage in your project.
250+
All four libraries work seamlessly with TanStack Form since they implement the Standard Schema protocol. Choose based on your preferences for bundle size, API style, or existing usage in your project.
251+
252+
> **Note:** Effect Schema requires wrapping with `Schema.standardSchemaV1()` for Standard Schema compliance. Tangrams handles this automatically in generated form options.
250253
251254
```typescript
252255
// Use Zod (default)
@@ -266,11 +269,17 @@ export default defineConfig({
266269
validator: "arktype",
267270
sources: [/* ... */],
268271
})
272+
273+
// Use Effect for the Effect ecosystem
274+
export default defineConfig({
275+
validator: "effect",
276+
sources: [/* ... */],
277+
})
269278
```
270279

271280
Install your chosen validator as a peer dependency:
272281

273-
<Tabs items={['Zod', 'Valibot', 'ArkType']}>
282+
<Tabs items={['Zod', 'Valibot', 'ArkType', 'Effect']}>
274283
<Tab value="Zod">
275284
```bash
276285
bun add zod
@@ -284,6 +293,11 @@ bun add valibot
284293
<Tab value="ArkType">
285294
```bash
286295
bun add arktype
296+
```
297+
</Tab>
298+
<Tab value="Effect">
299+
```bash
300+
bun add effect
287301
```
288302
</Tab>
289303
</Tabs>
@@ -353,7 +367,7 @@ generates: ["query", "form", "db"] // All three
353367

354368
Map custom GraphQL scalars to validator expressions. Values must be valid expressions for your configured validator library:
355369

356-
<Tabs items={['Zod', 'Valibot', 'ArkType']}>
370+
<Tabs items={['Zod', 'Valibot', 'ArkType', 'Effect']}>
357371
<Tab value="Zod">
358372
```typescript
359373
overrides: {
@@ -385,11 +399,22 @@ overrides: {
385399
Cursor: 'type("string")',
386400
},
387401
}
402+
```
403+
</Tab>
404+
<Tab value="Effect">
405+
```typescript
406+
overrides: {
407+
scalars: {
408+
DateTime: "Schema.String",
409+
JSON: "Schema.Unknown",
410+
Cursor: "Schema.String",
411+
},
412+
}
388413
```
389414
</Tab>
390415
</Tabs>
391416

392-
> **Note:** Scalar values must start with a valid prefix for your validator: `z.` for Zod, `v.` for Valibot, or `type(` / `type.` for ArkType. Using raw TypeScript types like `"string"` or `"Date"` will throw a validation error with a helpful suggestion.
417+
> **Note:** Scalar values must start with a valid prefix for your validator: `z.` for Zod, `v.` for Valibot, `type(` / `type.` for ArkType, or `Schema.` for Effect. Using raw TypeScript types like `"string"` or `"Date"` will throw a validation error with a helpful suggestion.
393418
394419
**Built-in Scalar Mappings:**
395420

bun.lock

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"@types/picomatch": "^4.0.2",
6969
"@vitest/coverage-istanbul": "^3.2.3",
7070
"arktype": "^2.1.20",
71+
"effect": "^3.12.0",
7172
"graphql-request": "^7.1.2",
7273
"tsup": "^8.5.0",
7374
"valibot": "^1.1.0",
@@ -78,6 +79,7 @@
7879
"@better-fetch/fetch": ">=1.0.0",
7980
"@tanstack/react-query": ">=5.0.0",
8081
"arktype": ">=2.0.0",
82+
"effect": ">=3.0.0",
8183
"graphql-request": ">=6.0.0",
8284
"valibot": ">=1.0.0",
8385
"zod": ">=4.0.0"
@@ -92,6 +94,9 @@
9294
"arktype": {
9395
"optional": true
9496
},
97+
"effect": {
98+
"optional": true
99+
},
95100
"graphql-request": {
96101
"optional": true
97102
},

packages/cli/src/adapters/graphql/graphql.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ const validatorPatterns: Record<
5656
string: '"string"',
5757
enum: "type.enumerated(",
5858
},
59+
effect: {
60+
import: 'import { Schema } from "effect"',
61+
object: "Schema.Struct(",
62+
string: "Schema.String",
63+
enum: "Schema.Union(Schema.Literal(",
64+
},
5965
};
6066

6167
// Test schema for unit tests (doesn't require network)
@@ -1245,6 +1251,15 @@ describe("GraphQL Collection Discovery", () => {
12451251
).not.toThrow();
12461252
});
12471253

1254+
it("accepts valid effect scalar expressions", () => {
1255+
expect(() =>
1256+
graphqlAdapter.generateSchemas(testSchemaWithScalar, scalarTestConfig, {
1257+
validator: "effect",
1258+
scalars: { Cursor: "Schema.String", DateTime: "Schema.String" },
1259+
}),
1260+
).not.toThrow();
1261+
});
1262+
12481263
it("throws error for invalid zod scalar with helpful message", () => {
12491264
expect(() =>
12501265
graphqlAdapter.generateSchemas(testSchemaWithScalar, scalarTestConfig, {
@@ -1299,6 +1314,24 @@ describe("GraphQL Collection Discovery", () => {
12991314
).toThrow(/Did you mean "type\("string"\)"\?/);
13001315
});
13011316

1317+
it("throws error for invalid effect scalar", () => {
1318+
expect(() =>
1319+
graphqlAdapter.generateSchemas(testSchemaWithScalar, scalarTestConfig, {
1320+
validator: "effect",
1321+
scalars: { Cursor: "string" },
1322+
}),
1323+
).toThrow(/Invalid scalar mapping for "Cursor": received "string"/);
1324+
});
1325+
1326+
it("suggests correct effect expression in error message", () => {
1327+
expect(() =>
1328+
graphqlAdapter.generateSchemas(testSchemaWithScalar, scalarTestConfig, {
1329+
validator: "effect",
1330+
scalars: { Cursor: "string" },
1331+
}),
1332+
).toThrow(/Did you mean "Schema\.String"\?/);
1333+
});
1334+
13021335
it("includes validator name in error message", () => {
13031336
expect(() =>
13041337
graphqlAdapter.generateSchemas(testSchemaWithScalar, scalarTestConfig, {

packages/cli/src/adapters/graphql/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ class GraphQLAdapterImpl implements IGraphQLAdapter {
210210
const result = generateFormOptionsCode(mutationOps, {
211211
schemaImportPath: options.schemaImportPath,
212212
formOverrides: options.formOverrides,
213+
validatorLibrary: options.validatorLibrary,
213214
});
214215

215216
return {

packages/cli/src/adapters/openapi/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ class OpenAPIAdapterImpl implements IOpenAPIAdapter {
151151
const result = generateFormOptionsCode(mutationOps, {
152152
schemaImportPath: options.schemaImportPath,
153153
formOverrides: options.formOverrides,
154+
validatorLibrary: options.validatorLibrary,
154155
});
155156

156157
return {

packages/cli/src/adapters/openapi/openapi.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ const validatorPatterns: Record<
5757
array: "type(",
5858
nullable: "| null",
5959
},
60+
effect: {
61+
import: 'import { Schema } from "effect"',
62+
object: "Schema.Struct(",
63+
string: "Schema.String",
64+
enum: "Schema.Union(Schema.Literal(",
65+
array: "Schema.Array(",
66+
nullable: "Schema.NullishOr(",
67+
},
6068
};
6169

6270
describe("OpenAPI Adapter", () => {

packages/cli/src/adapters/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ export interface FormGenOptions {
159159
sourceName: string;
160160
/** Form overrides from config (validator, validationLogic) */
161161
formOverrides?: FormOverridesConfig;
162+
/** Validation library (needed for Effect's Standard Schema wrapper) */
163+
validatorLibrary?: ValidatorLibrary;
162164
}
163165

164166
// =============================================================================

packages/cli/src/core/config.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,46 @@ describe("configSchema", () => {
455455
expect(result.data.output).toBe("./custom/output");
456456
}
457457
});
458+
459+
it("validates all supported validator libraries", () => {
460+
const validators = ["zod", "valibot", "arktype", "effect"] as const;
461+
for (const validator of validators) {
462+
const config = {
463+
validator,
464+
sources: [
465+
{
466+
name: "graphql",
467+
type: "graphql",
468+
schema: { url: "http://localhost:4000/graphql" },
469+
documents: "./src/graphql/**/*.graphql",
470+
generates: ["query"],
471+
},
472+
],
473+
};
474+
const result = configSchema.safeParse(config);
475+
expect(result.success).toBe(true);
476+
if (result.success) {
477+
expect(result.data.validator).toBe(validator);
478+
}
479+
}
480+
});
481+
482+
it("rejects invalid validator library", () => {
483+
const config = {
484+
validator: "invalid",
485+
sources: [
486+
{
487+
name: "graphql",
488+
type: "graphql",
489+
schema: { url: "http://localhost:4000/graphql" },
490+
documents: "./src/graphql/**/*.graphql",
491+
generates: ["query"],
492+
},
493+
],
494+
};
495+
const result = configSchema.safeParse(config);
496+
expect(result.success).toBe(false);
497+
});
458498
});
459499

460500
describe("defineConfig", () => {

0 commit comments

Comments
 (0)