import { it, describe, expect } from 'vitest'; const itEffect = (name: string, fn: () => Effect.Effect) => it(name, () => Effect.runPromise(fn())); import { Effect, Schema } from 'effect'; import { ESchema, EntityESchema, ValueESchema, toSchema } from '../index.js'; import { Snapshot } from '../../snapshot/index.js'; import { inspectESchema } from '../domain/introspection/index.js'; describe('ESchema draft version', () => { itEffect( 'decode returns the draft shape, encode writes the last published shape', () => Effect.gen(function* () { const schema = EntityESchema.make('Task', 'id', { title: Schema.String, }) .draft( { title: Schema.String, done: Schema.Boolean }, { forward: (previous) => ({ ...previous, done: false }), backward: (draft) => ({ id: draft.id, title: draft.title }), }, ) .build(); const decoded = yield* schema.decode({ _v: 'v1', id: 't1', title: 'write tests', }); expect(decoded).toEqual({ id: 't1', title: 'write tests', done: false, }); const encoded = yield* schema.encode({ id: 't1', title: 'write tests', done: true, }); expect(encoded).toEqual({ _v: 'v1', id: 't1', title: 'write tests' }); }), ); itEffect('draft types propagate through nested composition', () => Effect.gen(function* () { const Address = ESchema.make('Address', { street: Schema.String, }) .draft( { street: Schema.String, city: Schema.String }, { forward: (previous) => ({ ...previous, city: 'unknown' }), backward: (draft) => ({ street: draft.street }), }, ) .build(); const Status = ValueESchema.make('Status', Schema.Literal('published')) .draft(Schema.Literals(['published', 'draft']), { forward: (previous) => previous, backward: () => 'published' as const, }) .build(); const AddressSchema = toSchema(Address); const StatusSchema = toSchema(Status); const Profile = ESchema.make('Profile', { address: AddressSchema, status: StatusSchema, }).build(); const Account = ESchema.make('Account', { profiles: Schema.Array(toSchema(Profile)), }).build(); const decoded = yield* Account.decode({ _v: 'v1', profiles: [ { _v: 'v1', address: { _v: 'v1', street: 'Main Street' }, status: { _v: 'v1', value: 'published' }, }, ], }); expect(decoded).toEqual({ profiles: [ { address: { street: 'Main Street', city: 'unknown' }, status: 'published', }, ], }); const draftAccount: typeof Account.Type = { profiles: [ { address: { street: 'Main Street', city: 'New York' }, status: 'draft', }, ], }; const encoded = yield* Account.encode(draftAccount); expect(encoded).toEqual({ _v: 'v1', profiles: [ { _v: 'v1', address: { _v: 'v1', street: 'Main Street' }, status: { _v: 'v1', value: 'published' }, }, ], }); }), ); itEffect('a draft does not change what snapshot capture sees', () => Effect.gen(function* () { const published = EntityESchema.make('Task', 'id', { title: Schema.String, }).build(); const withDraft = EntityESchema.make('Task', 'id', { title: Schema.String, }) .draft( { title: Schema.String, done: Schema.Boolean }, { forward: (previous) => ({ ...previous, done: false }), backward: (draft) => ({ id: draft.id, title: draft.title }), }, ) .build(); const before = Snapshot.capture(published); const after = Snapshot.capture(withDraft); expect(after).toEqual(before); expect(Snapshot.diff(before, after)).toEqual([]); yield* Effect.void; }), ); itEffect( 'introspection reports only the published evolutions, unaffected by the draft', () => Effect.gen(function* () { const withDraft = EntityESchema.make('Task', 'id', { title: Schema.String, }) .draft( { title: Schema.String, done: Schema.Boolean }, { forward: (previous) => ({ ...previous, done: false }), backward: (draft) => ({ id: draft.id, title: draft.title }), }, ) .build(); const introspection = inspectESchema(withDraft); expect(introspection.evolutions).toHaveLength(1); expect(introspection.evolutions[0]?.version).toBe('v1'); yield* Effect.void; }), ); });