Nozbe / WatermelonDB

🍉 Reactive & asynchronous database for powerful React and React Native apps ⚡️
https://watermelondb.dev
MIT License
10.47k stars 587 forks source link

Unable to push sync TypeError: Cannot read property 'type' of undefined at _setRaw #1752

Open mbrimmer83 opened 7 months ago

mbrimmer83 commented 7 months ago

My pull works great and my push syncs to the server just fine, but my records _status are never marked as synced and I can't figure out why. The result is every time sync is called the same record is pushed to my server over and over again. I also can't figure out how to get logging working and there isn't anything in the docs about it.

const logger = new SyncLogger(20)
const log = logger.newLog()

interface WatermelonContextType {
  sync: (useTurbo?: boolean) => void
}

export const WatermelonContext = createContext<WatermelonContextType | null>(
  null
)
interface WatermelonProviderProps {
  children: JSX.Element | JSX.Element[] | null | boolean
}

const WatermelonProvider = ({ children }: WatermelonProviderProps) => {
  const [syncing, setSyncing] = useRecoilState(SyncState)
  const utils = trpc.useContext()
  const push = trpc.push.sync.useMutation()
  const isDeviceFirstSync = useMemo(() => {
    const result = storage.getString('isDeviceFirstSync')

    return !result
  }, [])

  const sync = useCallback(
    async (useTurbo = false) => {
      if (syncing) {
        return
      }
      setSyncing(true)
      await synchronize({
        database,
        log: log,
        migrationsEnabledAtVersion: 1,
        pullChanges: async ({ lastPulledAt, schemaVersion, migration }) => {
          if (useTurbo) {
            const response = await fetch(
              ENDPOINT
              {
                method: 'POST',
                body: JSON.stringify({
                  lastPulledAt,
                  schemaVersion,
                  migration
                })
              }
            )
            if (!response.ok) {
              throw new Error(await response.text())
            }
            const json = await response.text()
            return { syncJson: json }
          } else {
            const changes = await utils.pull.sync.fetch({
              lastPulledAt,
              schemaVersion,
              migration
            })

            return changes
          }
        },
        pushChanges: async ({ changes, lastPulledAt }) => {
          console.log('Push Time', lastPulledAt)
          const response = await push.mutateAsync({
            lastPulledAt,
            // @ts-ignore
            changes // TODO: We use superstruct to validate this on the server, but the types don't align
          })
          console.log('Push response', response)
          if (response.success) {
            return
          } else {
            throw new Error('Sync Push failed')
          }
        },
        sendCreatedAsUpdated: false,
        unsafeTurbo: useTurbo,
        onWillApplyRemoteChanges: async ({ remoteChangeCount }) => {
          console.log('Applying changes', remoteChangeCount)
          setSyncing(false)
        }
      })
    },
    [syncing, setSyncing, utils.pull.sync, push]
  )

  useEffect(() => {
    if (Env.APP_ENV !== 'development') {
      sync()
      if (isDeviceFirstSync) {
        storage.set('isDeviceFirstSync', JSON.stringify({ date: new Date() }))
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return (
    <WatermelonContext.Provider value={{ sync }}>
      {children}
    </WatermelonContext.Provider>
  )
}

As far as I can tell db.batch(...) never runs this code https://github.com/Nozbe/WatermelonDB/blob/44d89925985aca3fa72eef1df78f89356b1d9b6f/src/sync/impl/helpers.js#L97 Please help with debugging suggestions or issues with my implementation. Thanks

mbrimmer83 commented 7 months ago

The only thing I've read that would cause a pushed object not to marked as synced is if it changed during the sync and that hasn't happened in this case. All records are never marked as synced. Very Frustrating

mbrimmer83 commented 7 months ago

Ok, so I wrapped my entire sync function in a try catch and get an error inside watermelon TypeError: Cannot read property 'type' of undefined Top of stack inside _setRaw.

at _setRaw (http://10.0.0.14:8081/../../node_modules/@nozbe/watermelondb/RawRecord/index.bundle//&platform=ios&hot=false&lazy=true&transform.engine=hermes&transform.routerRoot=app&dev=true&minify=false&modulesOnly=true&runModule=false&shallow=true:20:28)
    at setRawSanitized (http://10.0.0.14:8081/../../node_modules/@nozbe/watermelondb/RawRecord/index.bundle//&platform=ios&hot=false&lazy=true&transform.engine=hermes&transform.routerRoot=app&dev=true&minify=false&modulesOnly=true&runModule=false&shallow=true:95:12)

When I debug further I found that the error was here

function _setRaw(raw, key, value, columnSchema) {
  console.log(raw, key, value, columnSchema)
  var {
    type: type,
    isOptional: isOptional
  } = columnSchema;
  ...

The result of that console.log is

[object] updated_at 1708213289147 undefined

What is interesting is I don't use updated_at in my schema. I elected to go with all camel cased fields in my schema and not use the updated_at or created_at fields as the docs said they weren't required. So why does this function get called with updated_at even though the columSchema is undefined? I think this may be a bug in Watermelon.

However, I do have updatedAt, createdAt, and deletedAt fields in my database, so maybe I should just map those fields and take advantage of watermelon db's built in advanced fields handling.

primus11 commented 7 months ago

Did you manage to solve this?

Didn't go into detail but there is explicit check for updatedAt. I don't think these fields are mandatory but if defined they should be defined both in model and in schema.

mbrimmer83 commented 7 months ago

Did you manage to solve this?

Didn't go into detail but there is explicit check for updatedAt. I don't think these fields are mandatory but if defined they should be defined both in model and in schema.

Yes, I added updated_at and created_at to my schema's and that solved the issue for me. But that is interesting, I assumed that it would be checking for updated_at and not updatedAt. I break from naming conventions in my database and use camelcase instead of snake case, which is why I used updatedAt

mbrimmer83 commented 7 months ago

Overall I don't like that Watermelon uses naming conventions like this. In my opinion you should be able to configure the columns you want to attach advanced field behaviors to. To my knowledge Sqlite doesn't care if you use snake-case or camelcase naming