Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

type-mongodb

Package Overview
Dependencies
Maintainers
1
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

type-mongodb - npm Package Compare versions

Comparing version 0.0.1-beta8 to 1.0.0-beta.0

lib/metadata/DiscriminatorMetadata.d.ts

8

lib/decorators/index.d.ts

@@ -17,3 +17,11 @@ import { Newable } from '../types';

export declare function Parent(): PropertyDecorator;
export interface AbstractDiscriminatorOptions {
property: string;
}
export interface DiscriminatorOptions {
value: string;
}
export declare function Discriminator(options: AbstractDiscriminatorOptions | DiscriminatorOptions): ClassDecorator;
export declare function MappedDiscriminator(type: string, discriminator: () => any): ClassDecorator;
export {};
//# sourceMappingURL=index.d.ts.map

46

lib/decorators/index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parent = exports.Field = exports.Document = void 0;
exports.MappedDiscriminator = exports.Discriminator = exports.Parent = exports.Field = exports.Document = void 0;
const definitionStorage_1 = require("../utils/definitionStorage");

@@ -44,2 +44,46 @@ function Document(options = {}) {

exports.Parent = Parent;
function Discriminator(options) {
return (target) => {
if (options.property) {
// this is the base abstract discriminator
const opts = options;
const mapping = {
DocumentClass: target,
propertyName: opts.property,
isMapped: true,
map: {}
};
definitionStorage_1.definitionStorage.discriminators.set(target, Object.assign(Object.assign({}, (definitionStorage_1.definitionStorage.discriminators.get(target) || {})), mapping));
}
else {
const opts = options;
let definition;
// locate abstract class
let proto = Object.getPrototypeOf(target);
while (proto && proto.prototype) {
definition = definitionStorage_1.definitionStorage.discriminators.get(proto);
if (definition) {
console.log(definition);
break;
}
proto = Object.getPrototypeOf(proto);
}
if (!definition) {
throw new Error(`Discriminator value "${target.name}" does not have a properly mapped base "@Discriminator()"`);
}
definition.map[opts.value] = () => target;
}
};
}
exports.Discriminator = Discriminator;
function MappedDiscriminator(type, discriminator) {
return (target) => {
const DocumentClass = discriminator();
const def = definitionStorage_1.definitionStorage.discriminators.has(DocumentClass)
? definitionStorage_1.definitionStorage.discriminators.get(DocumentClass)
: { DocumentClass, map: {} };
definitionStorage_1.definitionStorage.discriminators.set(DocumentClass, Object.assign(Object.assign({}, def), { map: Object.assign(Object.assign({}, def.map), { [type]: () => target }) }));
};
}
exports.MappedDiscriminator = MappedDiscriminator;
//# sourceMappingURL=index.js.map

@@ -10,2 +10,3 @@ import { DocumentClass, Newable } from '..';

private compiledFromDB;
private compiledInit;
private compiledMerge;

@@ -12,0 +13,0 @@ private constructor();

@@ -33,5 +33,6 @@ "use strict";

}
this.compiledToDB = this.createCompiler(false, true);
this.compiledFromDB = this.createCompiler(true, false);
this.compiledMerge = this.createCompiler(false, false);
this.compiledToDB = this.createCompiler('toDB', false, true);
this.compiledFromDB = this.createCompiler('fromDB', true, false);
this.compiledInit = this.createCompiler('init', false, false);
this.compiledMerge = this.createCompiler('merge', false, false);
this.isCompiled = true;

@@ -41,6 +42,28 @@ }

this.assertIsCompiled();
return this.compiledMerge(this.prepare(new this.meta.DocumentClass()), props, parent);
if (this.meta.discriminator) {
const { propertyName, mapping } = this.meta.discriminator;
return props[propertyName] && mapping.has(props[propertyName])
? mapping.get(props[propertyName]).transformer.init(props, parent)
: undefined;
}
return this.compiledInit(this.prepare(new this.meta.DocumentClass()), props, parent);
}
merge(model, props, parent) {
this.assertIsCompiled();
if (!model) {
return this.init(props, parent);
}
model = this.prepare(model);
if (this.meta.discriminator) {
const { propertyName, mapping } = this.meta.discriminator;
if (!props[propertyName] || !mapping.has(props[propertyName])) {
return;
}
const { DocumentClass, transformer } = mapping.get(props[propertyName]);
// when a discriminator type changes, it is brand new, so lets create
// it from scratch.
return model instanceof DocumentClass
? transformer.merge(model, props, parent)
: transformer.init(props, parent);
}
return this.compiledMerge(this.prepare(model), props, parent);

@@ -50,2 +73,8 @@ }

this.assertIsCompiled();
if (this.meta.discriminator) {
const { fieldName, mapping } = this.meta.discriminator;
return doc[fieldName] && mapping.has(doc[fieldName])
? mapping.get(doc[fieldName]).transformer.fromDB(doc, parent)
: undefined;
}
return this.compiledFromDB(new this.meta.DocumentClass(), doc, parent);

@@ -55,5 +84,11 @@ }

this.assertIsCompiled();
if (this.meta.discriminator) {
const { propertyName, mapping } = this.meta.discriminator;
return model[propertyName] && mapping.has(model[propertyName])
? mapping.get(model[propertyName]).transformer.toDB(model)
: undefined;
}
return this.compiledToDB({}, this.prepare(model));
}
createCompiler(isFromDB, isToDB) {
createCompiler(transformerFnName, isFromDB, isToDB) {
const context = new Map();

@@ -75,10 +110,12 @@ const has = (accessor) => {

const transformerFnVar = reserveVariable(`${fieldName}_transformer`);
const transformerFn = isToDB
? embeddedTransformer.toDB
: embeddedTransformer.fromDB;
const initTransformerFnVar = reserveVariable(`${fieldName}_init_transformer`);
const transformerFn = embeddedTransformer[transformerFnName];
context.set(transformerFnVar, transformerFn.bind(embeddedTransformer));
context.set(initTransformerFnVar, embeddedTransformer.init.bind(embeddedTransformer));
if (isEmbeddedArray) {
return `
if (${has(accessor)} && Array.isArray(source["${accessor}"])) {
target["${setter}"] = source["${accessor}"].map(v => ${transformerFnVar}(v, target));
${transformerFnName === 'merge'
? `target["${setter}"] = source["${accessor}"].map(v => ${transformerFnVar}(undefined, v, target));`
: `target["${setter}"] = source["${accessor}"].map(v => ${transformerFnVar}(v, target));`}
}

@@ -89,3 +126,5 @@ `;

if (${has(accessor)}) {
target["${setter}"] = ${transformerFnVar}(source["${accessor}"], target);
${transformerFnName === 'merge'
? `target["${setter}"] = ${transformerFnVar}(target["${setter}"], source["${accessor}"], target);`
: `target["${setter}"] = ${transformerFnVar}(source["${accessor}"], target);`}
}

@@ -92,0 +131,0 @@ `;

4

lib/metadata/AbstractDocumentMetadata.d.ts

@@ -6,2 +6,3 @@ import { ObjectId } from 'mongodb';

import { ParentDefinition } from './definitions';
import { DiscriminatorMetadata } from './DiscriminatorMetadata';
export declare type FieldsMetadata = Map<string, FieldMetadata>;

@@ -18,3 +19,4 @@ /**

readonly parent?: ParentDefinition;
constructor(DocumentClass: D, fields: FieldsMetadata, parent?: ParentDefinition);
readonly discriminator?: DiscriminatorMetadata;
constructor(DocumentClass: D, fields: FieldsMetadata, parent?: ParentDefinition, discriminator?: DiscriminatorMetadata);
addField(prop: FieldMetadata): void;

@@ -21,0 +23,0 @@ /**

@@ -11,3 +11,3 @@ "use strict";

class AbstractDocumentMetadata {
constructor(DocumentClass, fields, parent) {
constructor(DocumentClass, fields, parent, discriminator) {
this.DocumentClass = DocumentClass;

@@ -17,2 +17,3 @@ this.name = DocumentClass.name;

this.parent = parent;
this.discriminator = discriminator;
this.transformer = DocumentTransformer_1.DocumentTransformer.create(this);

@@ -60,3 +61,3 @@ }

init(props) {
return this.transformer.merge(new this.DocumentClass(), props);
return this.transformer.init(props);
}

@@ -63,0 +64,0 @@ /**

@@ -26,2 +26,11 @@ import { DocumentClass, Newable } from '../types';

}
export interface DiscriminatorDefinition<T = any> {
DocumentClass: DocumentClass<T>;
isMapped?: boolean;
propertyName?: string;
fieldName?: string;
map: {
[type: string]: () => Newable;
};
}
//# sourceMappingURL=definitions.d.ts.map

@@ -50,3 +50,5 @@ import { DocumentMetadata } from './DocumentMetadata';

protected buildFields(target: DocumentClass, fields?: FieldsMetadata): FieldsMetadata;
private locateParentDefinition;
private buildDiscriminatorMetadata;
}
//# sourceMappingURL=DocumentMetadataFactory.d.ts.map

@@ -19,2 +19,3 @@ "use strict";

const isPromise_1 = require("../utils/isPromise");
const DiscriminatorMetadata_1 = require("./DiscriminatorMetadata");
/**

@@ -132,3 +133,8 @@ * DocumentMetadataFactory builds and validates all the Document's metadata.

buildEmbeddedDocumentMetadata(DocumentClass) {
return new EmbeddedDocumentMetadata_1.EmbeddedDocumentMetadata(DocumentClass, this.buildFields(DocumentClass), definitionStorage_1.definitionStorage.parents.get(DocumentClass));
if (this.loadedEmbeddedDocumentMetadata.has(DocumentClass)) {
return this.loadedEmbeddedDocumentMetadata.get(DocumentClass);
}
const embeddedMetadata = new EmbeddedDocumentMetadata_1.EmbeddedDocumentMetadata(DocumentClass, this.buildFields(DocumentClass), this.locateParentDefinition(DocumentClass), this.buildDiscriminatorMetadata(DocumentClass));
this.loadedEmbeddedDocumentMetadata.set(DocumentClass, embeddedMetadata);
return embeddedMetadata;
}

@@ -152,3 +158,2 @@ /**

const embeddedMetadata = this.buildEmbeddedDocumentMetadata(embeddedType);
this.loadedEmbeddedDocumentMetadata.set(embeddedType, embeddedMetadata);
fields.set(prop.fieldName, new FieldMetadata_1.FieldMetadata(Object.assign(Object.assign({}, prop), { isEmbeddedArray,

@@ -160,10 +165,15 @@ embeddedMetadata,

}
let parent = Object.getPrototypeOf(target);
while (parent && parent.prototype) {
if (definitionStorage_1.definitionStorage.fields.has(parent)) {
this.buildFields(parent, fields);
// locate inherited decorated fields
let proto = Object.getPrototypeOf(target);
while (proto && proto.prototype) {
if (definitionStorage_1.definitionStorage.fields.has(proto)) {
this.buildFields(proto, fields);
}
parent = Object.getPrototypeOf(parent);
proto = Object.getPrototypeOf(proto);
}
if (!fields.size) {
// make the error more clear for discriminator mapped classes
if (definitionStorage_1.definitionStorage.discriminators.has(target)) {
DiscriminatorMetadata_1.DiscriminatorMetadata.assertValid(definitionStorage_1.definitionStorage.discriminators.get(target));
}
throw new Error(`"${target.name}" does not have any fields`);

@@ -173,4 +183,28 @@ }

}
locateParentDefinition(target) {
if (definitionStorage_1.definitionStorage.parents.get(target)) {
return definitionStorage_1.definitionStorage.parents.get(target);
}
// locate inherited `Parent()`
let proto = Object.getPrototypeOf(target);
while (proto && proto.prototype) {
if (definitionStorage_1.definitionStorage.parents.get(proto)) {
return definitionStorage_1.definitionStorage.parents.get(proto);
}
proto = Object.getPrototypeOf(proto);
}
}
buildDiscriminatorMetadata(target) {
if (!definitionStorage_1.definitionStorage.discriminators.has(target)) {
return;
}
const def = definitionStorage_1.definitionStorage.discriminators.get(target);
const map = new Map();
Object.keys(def.map).forEach((type) => {
map.set(type, this.buildEmbeddedDocumentMetadata(def.map[type]()));
});
return new DiscriminatorMetadata_1.DiscriminatorMetadata(def, map);
}
}
exports.DocumentMetadataFactory = DocumentMetadataFactory;
//# sourceMappingURL=DocumentMetadataFactory.js.map
import { DocumentClass } from '../types';
import { DocumentDefinition, FieldDefinition, ParentDefinition } from '../metadata/definitions';
import { DocumentDefinition, FieldDefinition, ParentDefinition, DiscriminatorDefinition } from '../metadata/definitions';
declare type FieldName = string;
declare type DocumentStorage = Map<DocumentClass<any>, DocumentDefinition>;
declare type FieldStorage = Map<DocumentClass<any>, Map<FieldName, FieldDefinition>>;
declare type ParentStorage = Map<DocumentClass<any>, ParentDefinition>;
declare type DocumentStorage = Map<DocumentClass, DocumentDefinition>;
declare type FieldStorage = Map<DocumentClass, Map<FieldName, FieldDefinition>>;
declare type ParentStorage = Map<DocumentClass, ParentDefinition>;
declare type DiscriminatorStorage = Map<DocumentClass, DiscriminatorDefinition>;
export declare const definitionStorage: {

@@ -11,4 +12,5 @@ documents: DocumentStorage;

parents: ParentStorage;
discriminators: DiscriminatorStorage;
};
export {};
//# sourceMappingURL=definitionStorage.d.ts.map

@@ -7,4 +7,5 @@ "use strict";

fields: new Map(),
parents: new Map()
parents: new Map(),
discriminators: new Map()
};
//# sourceMappingURL=definitionStorage.js.map
{
"name": "type-mongodb",
"version": "0.0.1-beta8",
"version": "1.0.0-beta.0",
"description": "A simple decorator based MongoDB ODM.",

@@ -5,0 +5,0 @@ "keywords": [],

@@ -5,2 +5,263 @@ <h1 align="center" style="border-bottom: none;">🔗 type-mongodb</h1>

**type-mongodb** makes it easy to map classes to MongoDB documents and back using `@decorators`.
```
## Features
- Extremely simply `@Decorator()` based document mapping
- Very fast 🚀! (thanks to JIT compilation)
- RAW. MongoDB is already extremely easy to use. It's best to use the driver
as it's intended. No validation, no change-set tracking, no magic -- just class mapping
- Custom Repositories
- Event Subscribers
- Transaction Support
- Discriminator Mapping
- & more!
## How to use
`type-orm` allows you to create a base document class for common functionality. Notice
that we don't enforce strict types. MongoDB is "schema-less", so we've decided to just
support their main types and not do anything fancy. Again, we wanted to keep it as close
to the core driver as possible.
```typescript
import { Field } from 'type-mongodb';
import { ObjectId } from 'mongodb';
abstract class BaseDocument {
@Field()
_id: ObjectId = new ObjectId();
get id(): string {
return this._id.toHexString();
}
@Field()
createdAt: Date = new Date();
@Field()
updatedAt: Date = new Date();
}
```
Now create our document class with some fields.
```typescript
import { Document, Field } from 'type-mongodb';
import { BaseDocument, Address, Pet } from './models';
@Document()
class User extends BaseDocument {
@Field()
name: string;
@Field(() => Address)
address: Address; // single embedded document
@Field(() => [Address])
addresses: Address[] = []; // array of embedded documents
@Field(() => [Pet])
pets: Pet[] = []; // array of discriminator mapped documents
@Field(() => [Pet])
favoritePet: Pet = []; // single discriminator mapped document
}
```
And here's the embedded `Address` document.
```typescript
import { Field } from 'type-mongodb';
class Address {
@Field()
city: string;
@Field()
state: string;
}
```
`type-mongodb` also has support for discriminator mapping (polymorphism). You do this
by creating a base class mapped by `@Discriminator({ property: '...' })` with a `@Field()` with the
name of the "property". Then decorate discriminator types with `@Discriminator({ value: '...' })`
and `type-mongodb` takes care of the rest.
```typescript
import { Discriminator, Field } from 'type-mongodb';
@Discriminator({ property: 'type' })
abstract class Pet {
@Field()
abstract type: string;
@Field()
abstract sound: string;
speak(): string {
return this.sound;
}
}
@Discriminator({ value: 'dog' })
class Dog extends Pet {
type: string = 'dog';
sound: string = 'ruff';
// dog specific fields & methods
}
@Discriminator({ value: 'cat' })
class Cat extends Pet {
type: string = 'cat';
sound: string = 'meow';
// cat specific fields & methods
}
```
And now, lets see the magic!
```typescript
import { DocumentManager } from 'type-mongodb';
import { User } from './models';
async () => {
const dm = await DocumentManager.create({
connection: {
uri: process.env.MONGO_URI,
database: process.env.MONGO_DB
},
documents: [User]
});
const repository = dm.getRepository(User);
await repository.create({
name: 'John Doe',
address: {
city: 'San Diego',
state: 'CA'
},
addresses: [
{
city: 'San Diego',
state: 'CA'
}
],
pets: [{ type: 'dog', sound: 'ruff' }],
favoritePet: { type: 'dog', sound: 'ruff' }
});
const users = await repository.find().toArray();
};
```
What about custom repositories? Well, that's easy too:
```typescript
import { Repository } from 'type-mongodb';
import { User } from './models';
export class UserRepository extends Repository<User> {
async findJohnDoe(): Promise<User> {
return this.findOneOrFail({ name: 'John Doe' });
}
}
```
Then register this repository with the `User` class:
```typescript
import { UserRepository } from './repositories';
// ...
@Document({ repository: () => UserRepository })
class User extends BaseDocument {
// ...
}
```
... and finally, to use:
```typescript
const repository = dm.getRepository<UserRepository>(User);
```
What about events? We want the base class to have createdAt and updatedAt be mapped
correctly.
```typescript
import {
EventSubscriber,
DocumentManager,
InsertEvent,
UpdateEvent
} from 'type-mongodb';
import { BaseDocument } from './models';
export class TimestampableSubscriber implements EventSubscriber<BaseDocument> {
// Find all documents that extend BaseDocument
getSubscribedDocuments?(dm: DocumentManager): any[] {
return dm
.filterMetadata(
(meta) => meta.DocumentClass.prototype instanceof BaseDocument
)
.map((meta) => meta.DocumentClass);
}
beforeInsert(e: InsertEvent<BaseDocument>) {
if (!e.model.updatedAt) {
e.model.updatedAt = new Date();
}
if (!e.model.createdAt) {
e.model.createdAt = new Date();
}
}
beforeUpdate(e: UpdateEvent<BaseDocument>) {
this.prepareUpdate(e);
}
beforeUpdateMany(e: UpdateEvent<BaseDocument>) {
this.prepareUpdate(e);
}
prepareUpdate(e: UpdateEvent<BaseDocument>) {
e.update.$set = {
updatedAt: new Date(),
...(e.update.$set || {})
};
e.update.$setOnInsert = {
createdAt: new Date(),
...(e.update.$setOnInsert || {})
};
}
}
```
...then register TimestampableSubscriber:
```typescript
const dm = await DocumentManager.create({
/// ...,
subscribers: [TimestampableSubscriber]
});
```
#### Other Common Features
```typescript
// custom collection and database
@Document({ database: 'app', collection: 'users' })
// using internal hydration methods
dm.toDB(User, user);
dm.fromDB(User, { /* document class */ });
dm.init(User, { /* user props */ });
dm.merge(User, user, { /* user props */ });
```
For more advanced usage and examples, check out the tests.

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc