livestorejs/livestore

[Bug]: State.SQLite.table(S.transform(..)) typing is wrong

Open

#680 opened on Sep 23, 2025

View on GitHub
 (2 comments) (0 reactions) (0 assignees)TypeScript (128 forks)github user discovery
bughelp wantedschematype:bug

Repository metrics

Stars
 (3,599 stars)
PR merge metrics
 (PR metrics pending)

Description

Bug Description

My stuff works after LS-197 thanks! The following is not blocking but just to document known issues that require me to do manual workarounds.

  1. S.optional on fields (of encoded side) gets represented as field: T | undefined instead of field?: T | undefined. This should correctly forward the ? part.

  2. select returns the encoded version of the encoded but claims it returns the decoded version of encoded. I.e. if the "from" side of the transform has a date: S.DateFromString then the actual value returned by select is string while typescript infers a Date. This should represent the reality - the types should infer encoded version of encoded.

    In the long run though, it should return the decoded version of the decoded side of the transfer, I saw your TODO. If you're thinking to make it at least return Date in the short term, don't because my workaround below would break (calling transform from one end to the other, so if you decode the "from" side then my end to end decode won't work).

    My workaround for the above is to create a separate transform which has both sides as deeply partial because I can specify dynamic fields in select and I assume schema would fail with missing fields. S.partial is shallow, so I have to type all nested objects manually and use S.extend(S.partial(MyStuff.omit('nestedObj')), S.Struct({nestedObj: S.optional(S.partial(MyStuffNestedObj)){))). Then I'd call decodeSync from it onto the result of select and that works.

  3. field: S.Literal('a', 'b') when used on the same field in both sides of S.transform() seems to only expect the first literal in the list, so if the value is 'b' then it'll say expected 'a', actual 'b'. This happened when I published a create event, then successfully encoded it (as per the bug 2 above) via S.encodeSync(MyTransform)(eventWithStructureMatchingDecoded), then passed the result into the insert and then I saw the above error which came from MyTransform. After I made the field into S.String on the encoded side, it worked.

    Update: this is actually reproducing on S.Struct tables too, so it's not just about S.transform. For non-transform, it worked after I changed S.Literal(...Object.values(MyActualEnum)) to this:

    const MyEnum = S.transform(S.String, S.Literal(...Object.values(MyActualEnum)), {
      strict: true,
      decode: input => input as MyActualEnum,
      encode: output => output
    })
    

LiveStore Version

0.4.0-dev.8

Which package(s) are affected?

@livestore/common

Contributor guide