npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@rocicorp/rails

v0.10.0

Published

CRUD interface generator for Replicache

Downloads

24,931

Readme

Replicache on Rails

Generates a CRUD-style interface for Replicache and Reflect, with optional schema validation.

Install

npm install --save-dev @rocicorp/rails

Usage

1. Define Entities

// All entities must include at least id:string.
export type Todo = {
  id: string;
  text: string;
  complete: boolean;
  sort: number;
};

2. Generate Helpers

import {generate} from '@rocicorp/rails';

export const {
  put: putTodo,
  get: getTodo,
  list: listTodos,
  // ...
} = generate<Todo>('todo');

3. Build Mutators

The generated functions all have the same signatures as mutators, so they can be used as mutators directly:

import {putTodo, updateTodo, deleteTodo} from './todo';

export const mutators = {
  putTodo,
  updateTodo,
  deleteTodo,
};

You can also compose them to make more advanced mutators:

import {listTodos, updateTodo} from './todo';

async function markAllComplete(tx: WriteTransaction) {
  const todos = await listTodos(tx);
  for (const t of todos) {
    // Mutators are transactional, so this is safe. The entire function will
    // run atomically and `t.complete` cannot change while it is running.
    if (!t.complete) {
      await updateTodo(todo.id, {complete: true});
    }
  }
}

export const mutators = {
  // ...
  markAllComplete,
};

4. Build Subscriptions

The generated functions that are read-only (get, has, list, etc) have the correct signature to be used as subscriptions. So you can use them directly:

// subscribe to a query
const todos = useSubscribe(rep, listTodos, []);

But as with mutators, you can also compose them to make more interesting subscriptions:

async function listIncompleteTodos(tx: WriteTransaction) {
  const todos = await listTodos(tx);
  return todos.filter(t => !t.complete);
}

const incompleteTodos = useSubscribe(rep, listIncompleteTodos, []);

Validation

You can optionally pass generate a validation function as a second parameter. For example, to use Zod as your schema validator:

import * as z from 'zod';
import {generate} from '@rocicorp/rails';

const todoSchema = {
  id: z.string(),
  text: z.string(),
  complete: z.boolean(),
  sort: z.number(),
};

// In this case, the template parameter to generate can be omitted because it
// is inferred from return type of todoSchema.parse().
export const {
  put: putTodo,
  get: getTodo,
  update: updateTodo,
  delete: deleteTodo,
  list: listTodos,
} = generate('todo', todoSchema.parse);

Reference

init(tx: WriteTransaction, value: T): Promise<boolean>

Writes value if it is not already present. If value is already present, does nothing. Returns true if the value was written or false otherwise.

put(tx: WriteTransaction, value: T): Promise<void>

Deprecated. Use set instead.

Writes value. If not present, creates, otherwise overwrites.

set(tx: WriteTransaction, value: T): Promise<void>

Writes value. If not present, creates, otherwise overwrites.

update(tx: WriteTransaction, value: Update<Entity, T>) => Promise<void>

Updates value. Value can specify any of the fields of T and must contain id. Fields not included in value are left unaffected.

All fields in an update are applied together atomically. If the entity does not exist a debug message is printed to the console and the update is skipped.

delete(tx: WriteTransaction, id: string) => Promise<void>

Delete any existing value or do nothing if none exist.

has(tx: ReadTransaction, id: string) => Promise<boolean>

Return true if specified value exists, false otherwise.

get(tx: ReadTransaction, id: string) => Promise<T | undefined>

Get value by ID, or return undefined if none exists.

mustGet(tx: ReadTransaction, id: string) => Promise<T>

Get value by ID, or throw if none exists.

list(tx: ReadTransaction, options?: {startAtID?: string, limit:? number}) => Promise<T[]>

List values matching criteria.

listIDs(tx: ReadTransaction, options?: {startAtID?: string, limit:? number}) => Promise<string[]>

List ids matching criteria.

listEntries(tx: ReadTransaction, options?: {startAtID?: string, limit:? number}) => Promise<[string, T][]>

List [id, value] entries matching criteria.

Presence State

Presence state is a special kind of state that is tied to a certain clientID. It is designed to be used with Reflect's presence feature.

The entity type for presence state is slightly different from the Entity type:

type PresenceEntity = {
  clientID: string;
  id: string;
};

Generate Presence State Helpers

The function generatePresence is similar to the generate function but it generates functions that are to be used with presence state.

type Cursor {
  clientID: string;
  x: number;
  y: number;
};

const {
  set: setCursor,
  get: getCursor,
  delete: deleteCursor,
} = generatePresence<Cursor>('cursor');

For presence entities there are two common cases:

  1. The entity does not have an id field. Then there can only be one entity per client. This case is useful for keeping track of things like the cursor position.
  2. The entity has an id field. Then there can be multiple entities per client. This case is useful for keeping track of things like multiple selection or multiple cursors (aka multi touch).

Lookup Presence State

The clientID field (and id if used) is significant when reading presence state. However, for convenience, you can omit the clientID and it will default to the clientID of current client. You may not omit the id if your entity type requires an id field. When reading presence state you may also omit the lookup argument completely.

Mutating Presence State

When writing you may only change the presence state entities for the current client. If you pass in a clientID that is different from the clientID of the current client a runtime error will be thrown.

When writing you may also omit the clientID it will default to the clientID of the current client.

await setCursor(tx, {x: 10, y: 20});
expect(await getCursor(tx)).toEqual({
  clientID: tx.clientID,
  x: 10,
  y: 20,
});

Reference for Presence State

set: (tx: WriteTransaction, value: OptionalClientID<T>) => Promise<void>

Write value, overwriting any previous version of same value.

init: (tx: WriteTransaction, value: OptionalClientID<T>) => Promise<boolean>

Write value only if no previous version of this value exists.

update: (tx: WriteTransaction, value: PresenceUpdate<T>) => Promise<void>

Update existing value with new fields.

delete: (tx: WriteTransaction, id?: PresenceID<T>) => Promise<void>

Delete any existing value or do nothing if none exist.

has: (tx: ReadTransaction, id?: PresenceID<T>) => Promise<boolean>

Return true if specified value exists, false otherwise.

get: (tx: ReadTransaction, id?: PresenceID<T>) => Promise<T | undefined>

Get value by ID, or return undefined if none exists.

mustGet: (tx: ReadTransaction, id?: PresenceID<T>) => Promise<T>

Get value by ID, or throw if none exists.

list: (tx: ReadTransaction, options?: PresenceListOptions<T>) => Promise<T[]>

List values matching criteria.

listIDs: (tx: ReadTransaction, options?: PresenceListOptions<T>) => Promise<ListID<T>[]>

List IDs matching criteria. The returned ID is {clientID: string} if the entry has no id field, otherwise it is {clientID: string, id: string}.

listClientIDs: (tx: ReadTransaction, options?: PresenceListOptions<T>) => Promise<string[]>

List clientIDs matching criteria. Unlike listIDs this returns an array of strings consisting of the clientIDs.

listEntries: (tx: ReadTransaction, options?: PresenceListOptions<T>) => Promise<[ListID<T>, T][]>

List [id, value] entries matching criteria.

Upgrade from 0.6

Pluggable Schema

Rails 0.7 made the schema validator pluggable. Instead of passing an instance of zod, pass the parse function.

Before:

export const {
  put: putTodo,
  // ...
} = generate<Todo>('todo', todoSchema);

Now:

export const {
  put: putTodo,
  // ...
} = generate<Todo>('todo', todoSchema.parse);

EntitySchema no longer part of Rails

Because the validator is pluggable it no longer makes sense for Rails to provide entitySchema. So either define it yourself:

const entitySchema = z.object({
  id: z.string(),
});

... or simply add id: z.string() to each of your entity definitions.

Parse called in Debug mode

In 0.6.0, zod was only used if process.env.NODE_ENV !== 'production'. Now that the validator is pluggable, it makes more sense for the app to do this.

Before:

export const {
  put: putTodo,
  // ...
} = generate<Todo>('todo', todoSchema);

Now:

export const {
  put: putTodo,
  // ...
} = generate<Todo>(
  'todo',
  process.env.NODE_ENV !== 'production' ? todoSchema.parse : undefined,
);