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

record-sculptor

v1.0.8

Published

A flexible library for serializing records into customizable views written in TypeScript. Inspired by Blueprinter, Record Sculptor aims to provide a simple yet powerful way to present and manipulate data objects.

Downloads

3

Readme

Record Sculptor: WORK IN PROGRESS!!!

A flexible library for serializing records into customizable views written in TypeScript. Inspired by Blueprinter, Record Sculptor aims to provide a simple yet powerful way to present and manipulate data objects.

Installation and Usage

Using npm:

npm install --save record-sculptor

Simplistic Usage

See examples/README.md for a more realistic example.

TODO: validate this code

// src/serializers/user-serialiser.ts
import { Serializer } from "record-sculptor"

import { User, Role } from "@/models"

import { RoleSerializer } from "@/serializers"

const UserSerializer = Serializer.define<User>(({ addView }) => {
  // Default view, put common stuff here, but prefer named views for anything specific or complex
  addView((view) => {
    view.addFields("id", "email", "firstName", "lastName", "isAdmin", "createdAt")

    view.addField("displayName", (user: User): string => `${user.firstName} ${user.lastName}`)
  })

  // Reuses all the fields from the default view, and adds a new roles field
  // TODO: implement this fallback to default feature
  addView("table", (view) => {
    view.addField("roles", (user: User): string[] => user.roles.map((r) => r.name))
  })

  // Reuses all the fields from the default view, and makes use of another serializer
  addView("detailed", (view) => {
    view.addField("roles", (user: User) => RoleSerializer.serialize(user.roles))
  })
})
// src/serializers/role-serializer.ts
import { serializer } from "record-sculptor"

import { Role } from "@/models"

class RoleSerializer extends Serializer<Role> {
  constructor(roleOrRoles: Role | Array<Role>) {
    super(roleOrRoles)
    this.addView((view) => {
      view.addFields("id", "userId", "name")
    })
  }
}
// src/routes.ts
import express, { type Request, type Response } from "express"

import { User } from "@/models"
import { UserSerializer } from "@/serializers"

export const router = express.Router()

router.get("/users", async (request: Request, response: Response) => {
  const users = await User.findAll() // Retrieval from database, using Sequelize in this example

  const serializedUsers = UserSerializer.serialize(users, { view: "table" }) // Data presentation/serialization

  return response.status(200).json({ users: serializedUsers }) // Send data
})

router.post("/users", async (request: Request, response: Response) => {
  const newAttributes = request.body

  return User.create(newAttributes)
    .then((user) => {
      // Save to database, using Sequelize in this example
      const serializedUser = UserSerializer.serialize(user, { view: "detailed" }) // Data presentation/serialization

      return response.status(201).json({ user: serializedUser }) // Send data
    })
    .catch((error) => {
      return response.status(422).json({ error: error.message }) // Handle errors
    })
})

router.get("/users/:id", async (request: Request, response: Response) => {
  const id = request.params.id
  const user = await User.findByPk(id) // Retrieval from database, using Sequelize in this example

  if (user === null) {
    return response.status(404).json({ message: "User not found" }) // Handle errors
  }

  const serializedUser = UserSerializer.serialize(user, { view: "detailed" }) // Data presentation/serialization

  return response.status(200).json({ user: serializedUser }) // Send data
})

router.put("/users/:id", async (request: Request, response: Response) => {
  const id = request.params.id
  const user = await User.findByPk(id) // Retrieval from database, using Sequelize in this example

  if (user === null) {
    return response.status(404).json({ message: "User not found" }) // Handle errors
  }

  const newAttributes = request.body
  return user
    .update(newAttributes)
    .then((updatedUser) => {
      const serializedUser = UserSerializer.serialize(updatedUser, { view: "detailed" }) // Data presentation/serialization

      return response.status(200).json({ user: serializedUser }) // Send data
    })
    .catch((error) => {
      return response.status(422).json({ error: error.message }) // Handle errors
    })
})

// Delete does use serializer as there is no point in returning anything
router.delete("/users/:id", async (request: Request, response: Response) => {
  const id = request.params.id
  // You _could_ peform a direct delete with Sequelize, but that makes the code harder to read,
  // and the optimization probably isn't worth it anyway.
  const user = await User.findByPk(id) // Retrieval from database, using Sequelize in this example

  if (user === null) {
    return response.status(404).json({ message: "User not found" }) // Handle errors
  }

  return user
    .destroy()
    .then(() => {
      return response.status(204).send() // Send empty response implies success
    })
    .catch((error) => {
      return response.status(422).json({ error: error.message }) // Handle errors
    })
})
// src/models/user.ts
import { Role } from "@/models"

class User {
  constructor(
    public id: number,
    public email: string,
    public firstName: string,
    public lastName: string,
    public isAdmin?: boolean,
    public createdAt?: Date,
    public roles?: Array<Role>,
  ) {}
}
// src/modles/role.ts
class Role {
  constructor(
    public id: number,
    public userId: number,
    public name: string,
  ) {}
}

Development

  1. Install asdf from https://asdf-vm.com/guide/getting-started.html

  2. Install the nodejs plugin for asdf via asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git

  3. Install the appropriate local nodejs version via asdf install nodejs

  4. Install the project dependencies via npm install

  5. Run the test suite via npm run test

Publishing

Testing Publishability

See https://www.freecodecamp.org/news/how-to-create-and-publish-your-first-npm-package/

  1. Build this project via npm run clean && npm run build

  2. Make a directory relative to the project directory called test-record-sculptor-import

  3. Change into the new project directory.

  4. Run npm link ../record-sculptor. Or if you are testing against a published version you can use npm install record-sculptor.

  5. Create a test-record-sculptor-import.ts file in the new project with this code in it.

    // test-record-sculptor-import/test-record-sculptor-import.ts
    
    import { Serializer } from "record-sculptor"
    
    class User {
      constructor(
        public id: number,
        public email: string,
        public firstName: string,
        public lastName: string,
        public isAdmin?: boolean,
        public createdAt?: Date, // public roles?: Array<Role>
      ) {}
    }
    
    const UserSerializer = Serializer.define<User>(({ addView }) => {
      addView((view) => {
        view.addFields("id", "email", "firstName", "lastName", "isAdmin", "createdAt")
    
        view.addField("displayName", (user: User): string => `${user.firstName} ${user.lastName}`)
      })
    })
    
    console.log(
      UserSerializer.serialize(
        new User(1, "[email protected]", "John", "Doe", true, new Date("2021-01-01T12:00:00Z")),
      ),
    )
  6. Run npx ts-node test-record-sculpture-import.ts and check that it prints the appropriate record info.

Publishing the Repo

See https://www.freecodecamp.org/news/how-to-create-and-publish-your-first-npm-package/

npm pack will generate the tar file that npm publish will publish, use it to test if you are publishing what you want to publish.

  1. Run npm login

  2. Run npm publish or npm publish --tag alpha (defaults to latest)

To update your version number:

  1. npm version patch (see https://docs.npmjs.com/about-semantic-versioning)

  2. npm run publish (see https://docs.npmjs.com/adding-dist-tags-to-packages)

  3. git push --tags to push release tags.

Future Development

TODO: move away from lodash after I've built the basics, so as to keep this project light.