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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@devtography/tyodm

v0.1.0-rc.4

Published

Fully typed ODM designed to work with various NoSQL database engines.

Readme

TyODM

npm version

Fully typed opinionated ODM inspired by Realm & Mongoose. Designed to be compatible with various NoSQL database engines.

Getting started

npm install @devtography/tyodm
import * as odm from '@devtography/tyodm';

class SampleModel extends odm.Obj {
  static SCHEMA: Schema = {
    name: 'SampleModel',
    props: {
      metadata: {
        type: 'single',
        attr: {
          name: 'string',
          desc: 'string?',
          active: 'bool',
          extra: { someNumber: 'double' },
        },
      },
      itemCollection: {
        type: 'collection',
        identifier: 'itemId',
        attr: { itemId: 'string', name: 'string' },
      },
    },
  };

  metadata?: {
    name: string,
    desc?: string,
    active: boolean,
    extra?: { someNumber: number }, // Nested object must be optional
  };
  itemCollection?: Map<string, { itemId: string, name: string }>;

  objectSchema(): Schema {
    return SampleModel.SCHEMA; // Expect to return the same schema from class.
  }
}

// Using DynamoDB as sample here.
// A table with `pk` as `HASH` & `sk` as `RANGE` is needed to be created first
// before connecting the `TyODM` instance to the target table.
const odm = new odm.TyODM({
  region: 'us-east-2',
  endpoint: 'http://localhost:8000',
  table: 'sample',
  schema: new Map<string, odm.Schema>([
    'SampleModel': SampleModel.SCHEMA,
  ]),
});

const obj = new SampleModel();
obj.metadata = { name: 'Sample Object', active: true };
obj.itemCollection = new Map([
  ['1', { itemId: '1', name: 'item 1' }],
  ['2', { itemId: '2', name: 'item 2' }],
]);

(async () => {
  await odm.attach();

  await odm.write(() => {
    obj.insertObj();
  });

  const objFromDb = await odm.objectByKey(SampleModel, obj.objectId);
  console.log(objFromDb);
})().catch((err) => {
  console.log(err);
});

Remarks

While you can have multiple tables/collections in your database, the way TyODM handles the data pretty much follows the single table design. The following example shows how the data schema looks like in your database correspond to the data models.

class Alpha extends odm.Obj {
  static SCHEMA: odm.Schema = { 
    name: 'Alpha',
    props: {
      singularRecord: {
        type: 'single',
        attr: { fieldA: 'string' },
      },
      subCat1: {
        type: 'collection',
        identifier: 'id',
        attr: { id: 'string', subFieldA: 'int' }
      },
      subCat2: {
        type: 'collection',
        identifier: 'id',
        attr: { id: 'string', subFieldA: 'double', subFieldB: 'int' },
      },
    }
   };

  singularRecord?: { fieldA: string };
  subCat1?: Map<string, { id: string, subFieldA: number }>;
  subCat2?: Map<string, { id; string, subFieldA: number, subFieldB: string }>;

  ...
}

| Collection | Sub collection | | | | |----------------------------------|----------------|------------|---------------|---------------| | | | fieldA | | | | Alpha#01FCBEBM470CA8BN7B2H95SQ7X | singularRecord | 'abc' | | | | | | id | subFieldA | | | | subCat1#01A | '01A' | 10 | | | | subCat1#01B | '01B' | 11 | | | | | id | subFieldA | subFieldB | | | subCat2#02A | '02A' | 7.62 | 1 | | | subCat2#02B | '02B' | 0.45 | 2 |

Custom identifier & constructor

For the class/collection level unique identifier, by default there's an ULID generated for each instance on initialisation. Alternatively, a custom identifier can also be set via the constructor as following:

class Beta extends odm.Obj {
  static SCHEMA: odm.Schema = {
    name: 'Beta',
    identifier: 'customId',
    props: { ... }
  };

  customId: string;

  constructor(objId?: string) {
    // Only constructor with optional parameters is allowed. 1st parameter
    // must be an optional string and needs to be passed to the parent 
    // constructor so the internal functions can set the `objectId` properly.
    // Constructor with non optional parameters will result in runtime exception.
    super(objId);

    // It is important to assign value to your custom identifier here instead
    // of doing it on the line you declare the property. Otherwise the value of
    // you custom identifier will be overwritten by the value assigned there
    // when retrieve the object from database.
    this.customId = 'your custom unique identifier';
  }

  ...
}

Be caution, by setting your own class/collection level identifier, you must make sure its' value for each instance under the same class is unique, otherwise you might corrupt your data on the table.

In terms of sub-collection level identifier, there's no default provided like the class/collection level one. The sub-collection level will have to be specified in the schema as following:

class Charlie extends odm.Obj {
  static SCHEMA: odm.Schema = {
    name: 'Charlie',
    props: {
      sub: {
        type: 'collection',
        identifier: 'relatedAlpha',
        attr: { relatedAlpha: 'string', isCharlie: 'bool' },
      },
    },
  };

  sub: Map<string, { relatedAlpha: string, isCharlie: boolean }>
  
  ...
}

// console.log
//   Charlie {
//     sub: { relatedAlpha: '01FCBEBM470CA8BN7B2H95SQ7X', isCharlie: true )
//   }

The value can only be string for both class/collection and sub-collection level identifiers. Exception may be thrown if otherwise.

Support the project

Contributions via pull requests are welcome and encouraged. If there's anything you consider essential that should be included in this boilerplate, please don't hesitate to implement yourself and make a pull request :)

Same as the other open sources projects in @Devtography, I maintain & further develop this boilerplate with my free time. If you found my work useful and wanna support me keep on doing all these, please consider donate/sponsor me.

Author

Wing Chau @Devtography

License

TyODM is open source software licensed as MIT.