Ideas draft
June 14, 2026 · View on GitHub
v11
ideas
- Add
promisetype andS.promise(instead of async flag internally)
TODO:
Test null<> in ppx
// Test that refinement works correctly with reverse
S.reverse(S.schema({
foo: S.string->S.to(S.number)
})->S.refine(value => value.foo > 0))
TS operation functions
-
rename
serializerto reverse parser ? -
Make
foo->S.to(S.unknown)stricter ?? -
Add
S.to(from, target, parser, serializer)instead ofS.transform? -
Make built-in refinements not work with
unknown. UseS.to(manually & automatically) to deside the type first -
Better inline empty recursive schema operations (union convert)
-
Don't iterate over JSON value when it's
S.jsonconvert without parsing -
Add
S.date.with(S.migrationFrom, S.string, <optionalParser>). -
Allow to pass {} instead of S.schema({}) to S.array and other schemas
Final release fixes
- Add
S.envto support coercion for union items separately. Likerescript-envsafeused to do withpreprocess - Make
S.recordaccept two args - Update docs
Known bugs left over from the validation refactor (val.validation: array<validationCheck>)
- Union discriminant hoists refinement checks with
&&instead of;. Now that refinements are structured checks, the union item merge loop hoists all checks on a val viaandJoinChecks, fusing type checks and refinement checks into one&&-joined condition with a single error throw. This causes two problems: (1)typeof==="string"&&length===Nshares one error instead of separate type/refinement errors, and (2) same-type items with different refinements (e.g.S.union([S.string->S.email, S.string->S.url])) lose per-item error messages. Fix: split hoisted checks byfailreference — first group (type checks) → discriminant condition, remaining groups (refinement checks) → body code ascond||fail;. For same-type items with different refinements, use if/else if dispatch on the refinement cond instead of try/catch. Failing regression tests inS_union_test.res. noValidationon a literal inside a union silently breaks dispatch.literalDecodershort-circuits whenexpectedSchema.noValidationis set and emits no check at all, so there's nothing for the union discriminant hoister to lift — that case becomes a catch-all. Fix: either emit the equality check regardless ofnoValidationwhen the val ends up inside a union, or rejectS.noValidationon a literal-in-union at schema construction time. Failing regression test:S_noValidation_test.res › Union dispatch still works when a case has noValidation.err.receivedis wrong for refine-chain vals on type failures. BecauseB.refinesets~schema=prev.expected,val.schemaon a refined val equals the target schema, andfailInvalidTypereadsval.schemaforreceived. Soerr.received === err.expectedon a primitive type failure. User-visible reason text is unaffected (it usesinput->stringify) but programmatic consumers readingerr.receivedget the target schema instead of the source type. Fix: either have the fail function reach throughval.prev.schema(with a comment on the invariant that validation-owning vals always have a prev) or stop mutatingval.schemato the target inrefineand walk the chain differently for "Expected X" messages. FIXME is tagged atSury.res:failInvalidType.
v11 initial
- Add
s.parseChildto EffectContext ??? - Support arrays for
S.to - Remove fieldOr in favor of optionOr?
- Allow to pass custom error message via
.with - Make S.to extensible
Add S.Date (S.instanceof) and remove S.datetime(S.date added; S.datetime kept for backward compat)- Add refinement info to the tagged type
v???
S.promise: S.t<'value> => S.t<promise<'value>>andS.await: S.t<promise<'value>> => S.t<'value>- Remove
S.deepStrictandS.deepStripin favor ofS.deep(if it works) - Make S.serializeToJsonString super fast
- Somehow determine whether transformed or not (including shape)
- Add JSDoc
- s.optional for object
- S.transform(s => { s.reverse(input => input) // Or s.asyncReverse(input => Promise.resolve(input)) input => input }) // or asyncTransform // Maybe format ?
- Clean up Caml_option.some, Js_dict.get
- Github Action: Add linter checking that the generated files are up to date (?)
- Support optional fields (can have problems with serializing) (???)
- S.mutateWith/S.produceWith (aka immer) (???)
- Add S.function (?) (An alternative for external ???)
let trimContract: S.contract<string => string> = S.contract(s => {
s.fn(s.arg(0, S.string))
}, ~return=S.string)
- Use internal transform for trim
- Add schema input to the error ??? What about build errors?
- async serializing support
- Add S.promise
- S.create / S.validate
- Add S.codegen
- Rename S.inline to S.toRescriptCode + Codegen type + Codegen schema using type
- Make
error.reasontree-shakeable - S.toJSON/S.castToJson ???
- S.produce
- S.mutator
- Check only number of fields for strict object schema when fields are not optional (bad idea since it's not possible to create a good error message, so we still need to have the loop)
Articles
- Write an article about creating an AI-friendly JS library (how the API design, type overloads like
S.is/S.assertaccepting both arg orders, and error messages make Sury easy for both humans and LLMs to use)