npm.io
0.5.0 • Published 1 week ago

teamplay

Licence
MIT
Version
0.5.0
Deps
17
Size
424 kB
Vulns
0
Weekly
0

TeamPlay

Full-stack signals ORM with multiplayer

Features:

  • signals *
  • multiplayer **
  • ORM
  • auto-sync data from client to DB and vice-versa ***
  • query DB directly from client ***
  • works in pure JS, on server (Node.js) and integrates with React

* deep signals -- with support for objects and arrays
** concurrent changes to the same data are auto-merged using OT
*** similar to Firebase but with your own MongoDB database

Installation

For installation and documentation see teamplay.dev

ORM Helpers

For legacy Racer-style model mixins (for example versioning libraries which call getAssociations()), use ORM helpers from the teamplay/orm subpath:

import BaseModel, { hasMany, hasOne, belongsTo } from 'teamplay/orm'

These helpers attach class-level associations and expose them through $doc.getAssociations() on model signals.

React Suspense Gates

If you need to throw a thenable from render, prefer useSuspendMemo() or useSuspendMemoByKey() over useMemo().

Why:

  • React may restart a suspended initial render.
  • useMemo() is not a semantic "run this suspend gate once" primitive.
  • Side-effectful async work like join() may accidentally start again on retry.
useSuspendMemo(factory, deps)

Use it when the suspend gate is local to one observer component instance.

import { observer, useSuspendMemo } from 'teamplay'

const Component = observer(({ $stage, userId, stageUserStore }) => {
  useSuspendMemo(() => {
    if (!stageUserStore?.startedAt) {
      throw $stage.join(userId)
    }
  }, [$stage.getId()])

  return <span>Ready</span>
})

This keeps the same pending thenable for the same hook slot while the component instance is alive.

useSuspendMemoByKey(key, factory, deps)

Use it when the async operation must be deduped by business meaning, not just by component instance.

import { observer, useSuspendMemoByKey } from 'teamplay'

const Component = observer(({ $stage, stageId, userId, stageUserStore }) => {
  useSuspendMemoByKey(
    `stage.join:${stageId}:${userId}`,
    () => {
      if (!stageUserStore?.startedAt) {
        throw $stage.join(userId)
      }
    },
    [stageId, userId, !!stageUserStore?.startedAt]
  )

  return <span>Ready</span>
})

This is the right choice when:

  • the component may remount while the promise is still pending;
  • two different components may trigger the same async operation;
  • the operation should behave like a single in-flight business task.

License

MIT