Skip to content

Instantly share code, notes, and snippets.

@adamchel
Last active February 26, 2019 20:27
Show Gist options
  • Save adamchel/96bf0d6da268ea252f078a40fce523ec to your computer and use it in GitHub Desktop.
Save adamchel/96bf0d6da268ea252f078a40fce523ec to your computer and use it in GitHub Desktop.
Small sample application demonstrating custom Codecs with MongoDB Stitch TypeScript SDK
import {
AnonymousCredential,
BSON,
Codec,
Stitch,
RemoteMongoClient,
RemoteMongoCollection
} from 'mongodb-stitch-server-sdk';
// This is the interface we want to insert and retrieve from the collection.
interface CustomObject {
_id: BSON.ObjectId,
owner_id: string,
message: string,
mixed_type: string | number,
}
// This is the codec that should be passed to the Stitch SDK to handle
// the conversion of BSON documents from the database into objects that
// match the interface. CustomObject could also be a class instead of an
// interface, if you wanted to have methods on the objects that get inserted
// into and retrieved from the database.
class CustomObjectCodec implements Codec<CustomObject> {
// This is a user-defined type guard where you can verify that the
// object returned from MongoDB is valid.
// see: https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards
isCustomObject(obj: any): obj is CustomObject {
return obj._id !== undefined && obj._id instanceof BSON.ObjectID
&& obj.owner_id !== undefined && (typeof obj.owner_id === "string")
&& obj.message !== undefined && (typeof obj.message === "string")
&& obj.mixed_type !== undefined
&& ((typeof obj.mixed_type === "string") || typeof obj.mixed_type === "number");
}
// Decodes a document/object from the collection into a CustomObject. This is
// where you can implement any advanced decoding logic you might need. For this
// sample, we just verify that the input document has the correct types.
decode(from: any): CustomObject {
if(this.isCustomObject(from)) {
return from;
}
// the check is not strictly necessary, but it lets you throw a
// customized error if the document you get back from the database does
// not match what you were expecting, instead of getting an unexpected
// `undefined` elsewhere in your code
throw new Error("object not valid")
}
// Encodes the provided CustomObject into a plain JS object before it gets.
// inserted into the MongoDB database. This is also where you can modify
// any fields before they get inserted into the database.
encode(from: CustomObject): object {
return {
_id: from._id,
owner_id: from.owner_id,
message: from.message,
mixed_type: from.mixed_type
}
}
}
// This is just a class that implements CustomObject, so we can demonstrate how
// you can actually insert not just interfaces, but also class instances.
class ConcreteCustomObject implements CustomObject {
_id: BSON.ObjectId;
owner_id: string;
message: string;
mixed_type: string | number;
constructor(owner_id: string, message: string, mixed_type: string | number) {
this._id = new BSON.ObjectId();
this.owner_id = owner_id;
this.message = message;
this.mixed_type = mixed_type;
}
}
// This logs into the Stitch app, and demonstrates using a CustomObject
// collection, and getting back a CustomObject from a Stitch function. This
// requires a Stitch app with a rule on `custom.items`, and a Stitch function
// called `getCustomObject` which returns an object that can be casted to
// a TypeScript interface.
const client = Stitch.initializeDefaultAppClient('your-client-app-id');
client.auth.loginWithCredential(new AnonymousCredential()).then(user => {
console.log(user.id);
demonstrateCollectionWithCustomType()
demonstrateFunctionWithCustomType()
client.close();
}).catch((err: any) => {
console.log(err);
client.close();
})
function demonstrateCollectionWithCustomType() {
let mongoClient = client.getServiceClient(
RemoteMongoClient.factory, "mongodb-atlas"
);
// create a collection that uses the CustomObjectCodec
// to encode and decode from the collection
let coll: RemoteMongoCollection<CustomObject> = mongoClient
.db("custom")
.collection("items", new CustomObjectCodec());
let customObjToInsert1: CustomObject = new ConcreteCustomObject(
client.auth.user!.id,
"this is a JS class",
42
);
let customObjToInsert2: CustomObject = new ConcreteCustomObject(
client.auth.user!.id,
"this is also a JS class",
"43"
);
// we can insert the custom objects directly into the collection
coll.insertMany([customObjToInsert1, customObjToInsert2]).then(() => {
// and then we can read `CustomObject`s directly from the collection
return coll.find().first()
}).then(thisIsACustomObj => {
if (thisIsACustomObj === undefined) {
throw new Error("could not find any objects");
}
// now we can access this as if it were a `CustomObject`
console.log(thisIsACustomObj._id);
console.log(thisIsACustomObj.message);
console.log(thisIsACustomObj.mixed_type);
console.log(thisIsACustomObj.owner_id);
});
}
function demonstrateFunctionWithCustomType() {
// NOTE: this is just a cast, not actually a conversion using the
// Codec. this means that if the function returns an invalid CustomObject,
// then you might get errors elsewhere in the code. We may add support for
// Codecs for callFunction in the future. Let us know if it's something you
// would find useful!
client.callFunction("getCustomObj", []).then((customObjResult: CustomObject) => {
// now we can access this as if it were a `CustomObject`
console.log(customObjResult._id);
console.log(customObjResult.message);
console.log(customObjResult.mixed_type);
console.log(customObjResult.owner_id);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment