Giter Site home page Giter Site logo

sebelga / gstore-node Goto Github PK

View Code? Open in Web Editor NEW
291.0 17.0 52.0 2.36 MB

Google Datastore Entities Modeling for Node.js

License: Apache License 2.0

JavaScript 0.85% Shell 0.04% TypeScript 99.12%
google-datastore entity-model schema entity gstore nodejs modeling-tool

gstore-node's Introduction

gstore-node Tweet

Entities modeling for Google's Datastore

NPM Version Build Status coveralls-image Commitizen friendly

πŸŽ‰ NEWS: new maintainer!

A few weeks ago I announced that gstore-node was deprecated as I currently don't have bandwidth to work on it. A few days later, Hendrik Schalekamp stepped in and offered to be a maintainer of the project. I was thrilled! 😊 Hendrik will be the lead of the project and I will be around to provide any necessary guidance. Thanks Hendrik!

Documentation | Example | Demo Application | Support | Changelog

gstore-node is a Google Datastore entities modeling library for Node.js inspired by Mongoose and built on top of the @google-cloud/datastore client.
It is not a replacement of @google-cloud/datastore but a layer on top of it to help modeling your entities through Schemas and to help validating the data saved in the Datastore.

Highlight

  • explicit Schema declaration for entities
  • properties type validation
  • properties value validation
  • shortcuts queries
  • pre & post middleware (hooks)
  • custom methods on entity instances
  • Joi schema definition and validation
  • Advanced cache layer
  • Typescript support
  • populate() support to fetch reference entities and do cross Entity Type "joins" when querying one or multiple entities (since v5.0.0)

Please don’t forget to star this repo if you find it useful :)

Installation

npm install gstore-node --save
# or
yarn add gstore-node

Important: gstore-node requires Node version 8+

Getting started

Import gstore-node and @google-cloud/datastore and configure your project.
For the information on how to configure @google-cloud/datastore read the docs here.

const { Gstore } = require('gstore-node');
const { Datastore } = require('@google-cloud/datastore');

const gstore = new Gstore();
const datastore = new Datastore({
    projectId: 'my-google-project-id',
});

// Then connect gstore to the datastore instance
gstore.connect(datastore);

After connecting gstore to the datastore, gstore has 2 aliases set up

  • gstore.ds
    The @google/datastore instance. This means that you can access all the API of the Google library when needed.

  • gstore.transaction. Alias of the same google-cloud/datastore method

The complete documentation of gstore-node is in gitbook.
If you find any mistake in the docs or would like to improve it, feel free to open a PR.

Initialize gstore-node in your server file

// server.js

const { Gstore, instances } = require('gstore-node');
const { Datastore } = require('@google-cloud/datastore');

const gstore = new Gstore();
const datastore = new Datastore({
    projectId: 'my-google-project-id',
});

gstore.connect(datastore);

// Save the gstore instance
instances.set('unique-id', gstore);

Create your Model

// user.model.js

const { instances } = require('gstore-node');
const bscrypt = require('bcrypt-nodejs');

// Retrieve the gstore instance
const gstore = instances.get('unique-id');
const { Schema } = gstore;

/**
 * A custom validation function for an embedded entity
 */
const validateAccessList = (value, validator) => {
    if (!Array.isArray(value)) {
        return false;
    }

    return value.some((item) => {
        const isValidIp = !validator.isEmpty(item.ip) && validator.isIP(item.ip, 4);
        const isValidHostname = !validator.isEmpty(item.hostname);

        return isValidHostname && isValidIp;
    });
}

/**
 * Create the schema for the User Model
*/
const userSchema = new Schema({
    firstname: { type: String, required: true },
    lastname: { type: String, optional: true  },
    email: { type: String, validate: 'isEmail', required: true },
    password: { type: String, read: false, required: true },
    createdOn: { type: String, default: gstore.defaultValues.NOW, write: false, read: false },
    address: { type: Schema.Types.Key, ref: 'Address' }, // Entity reference
    dateOfBirth: { type: Date },
    bio: { type: String, excludeFromIndexes: true },
    website: { validate: 'isURL', optional: true },
    ip: {
        validate: {
            rule: 'isIP',
            args: [4],
        }
    },
    accessList: {
        validate: {
            rule: validateAccessList,
        }
    },
});

// Or with **Joi** schema definition
// You need to have joi as a dependency of your project ("npm install joi --save")
const userSchema = new Schema({
    firstname: { joi: Joi.string().required() },
    email: { joi: Joi.string().email() },
    password: { joi: Joi.string() },
    ...
}, {
    joi: {
        extra: {
            // validates that when "email" is present, "password" must be too
            when: ['email', 'password'],
        },
    }
});

/**
 * List entities query shortcut
 */
const listSettings = {
    limit: 15,
    order: { property: 'lastname' }
};
userSchema.queries('list', listSettings);

/**
 * Pre "save" middleware
 * Each time the entity is saved or updated, if there is a password passed, it will be hashed
*/
function hashPassword() {
    // scope *this* is the entity instance
    const _this = this;
    const password = this.password;

    if (!password) {
        return Promise.resolve();
    }

    return new Promise((resolve, reject) => {
        bcrypt.genSalt(5, function onSalt(err, salt) {
            if (err) {
                return reject(err);
            };

            bcrypt.hash(password, salt, null, function onHash(err, hash) {
                if (err) {
                    // reject will *not* save the entity
                    return reject(err);
                };

                _this.password = hash;

                // resolve to go to next middleware or save method
                return resolve();
            });
        });
    });
}

// add the "pre" middleware to the save method
userSchema.pre('save', hashPassword);

/**
 * Export the User Model
 * It will generate "User" entity kind in the Datastore
*/
module.exports = gstore.model('User', userSchema);

Use it in your Controller

// user.constroller.js

const User = require('./user.model');

const getUsers = (req ,res) => {
    const pageCursor = req.query.cursor;

    // List users with the Query settings defined on Schema
    User.list({ start: pageCursor })
        .then((entities) => {
            res.json(entities);
        })
        .catch(err => res.status(400).json(err));
};

const getUser = (req, res) => {
    const userId = +req.params.id;
    User.get(userId)
        .populate('address') // Retrieve the reference entity
        .then((entity) => {
            res.json(entity.plain());
        })
        .catch(err => res.status(400).json(err));
};

const createUser = (req, res) => {
    const entityData = User.sanitize(req.body);
    const user = new User(entityData);

    user.save()
        .then((entity) => {
            res.json(entity.plain());
        })
        .catch((err) => {
            // If there are any validation error on the schema
            // they will be in this error object
            res.status(400).json(err);
        })
};

const updateUser = (req, res) => {
    const userId = +req.params.id;
    const entityData = User.sanitize(req.body); // { email: '[email protected]' }

    /**
     * This will fetch the entity, merge the data and save it back to the Datastore
    */
    User.update(userId, entityData)
        .then((entity) => {
            res.json(entity.plain());
        })
        .catch((err) => {
            // If there are any validation error on the schema
            // they will be in this error object
            res.status(400).json(err);
        });
};

const deleteUser = (req, res) => {
    const userId = +req.params.id;
    User.delete(userId)
        .then((response) => {
            res.json(response);
        })
        .catch(err => res.status(400).json(err));
};

module.exports = {
    getUsers,
    getUser,
    createUser,
    updateUser,
    deleteUser
};

If you want to see an example on how to use gstore-node in your Node.js app, check the demo blog application repository.

Development

  1. Install npm dependencies
yarn install
  1. Run the unit tests
yarn test:unit
  1. Run the integration tests

Prerequise:
In order to run the integration tests you need to have the Google Datastore Emulator installed as well as Redis.

  • Launch the Redis server
# From the folder where you've installed the Redis SDK run: 
cd src && ./redis-server
  • Launch the Datastore Emulator (separate terminal window)
# From the root of the project
yarn local-datastore
  • Execute the integration tests (separate terminal window)
# From the root of the project
yarn test:integration

Meta

SΓ©bastien Loix – @sebloix
Hendrik Schalekamp - @carnun

Distributed under the MIT license. See LICENSE for more information.

Contributing

  1. Fork it (https://github.com/sebelga/gstore-node/fork)
  2. Create your feature branch (git checkout -b feature/fooBar)
  3. Generate a conventional commit message (npm run commit)
  4. Push to the branch (git push origin feature/fooBar)
  5. Rebase your feature branch and squash (git rebase -i master)
  6. Create a new Pull Request

Credits

I have been heavily inspired by Mongoose to write gstore. Credits to them for the Schema, Model and Entity definitions, as well as 'hooks', custom methods and other similarities found here. Not much could neither have been done without the great work of the guys at googleapis.

gstore-node's People

Contributors

baskiers avatar carnun avatar dependabot[bot] avatar dmk1111 avatar jfbenckhuijsen avatar jgeewax avatar lesmo avatar maher4ever avatar maininfection avatar micaww avatar mickdekkers avatar pdesgarets avatar pindar avatar raynux avatar sarunyou avatar sarunyouwhang avatar sebelga avatar soraxtend avatar the-alchemist avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gstore-node's Issues

Optional Promise-returning methods

Hi,

Love what's been done here, this is just the kind of thing I've been looking for after moving from Java (using the objectify library for Google datastore) into Javascript Node development and finding the implementation a bit bare-bones in the base google-cloud library. The schema and model concepts really make it easy to work with.

The only thing I'd really love to see added is the option of having Promises returned instead of using callbacks (on the relevant such methods), such as the Model methods get, updated, delete etc. It could be added using a different name such as BlogPost.getAsync(1234). I like to make use of async functions and combined with promises it makes things amazingly easy to reason about.

For now I'll have to wrap all the base methods in Promises myself, but would be nice to have it out the box.

Order does not work in a Date field

order does not work in a datetime field

 const ProjectSchema = new Schema({
    name:     {type: 'string'},
    members:  {type: 'object'},
    createOn: {type: 'datetime', default: gstore.defaultValues.NOW},
    updateOn: {type: 'datetime', default: gstore.defaultValues.NOW},
    files: {type: 'object'}
  });

I tried the following query

const query =
        db
          .ProjectModel
          .query()
          .order('updateOn', {
            descending: true
          })
          .limit(20);

It is not delivered in the expected order.

Query settings not creating appropriate index

Hey there! I think this is a bug, as the documentation does not mention having to create indexes for queries.

I have an example model that looks like this:

const gstore = require('gstore-node')();
const Schema = gstore.Schema;

const itemSchema = new Schema({
    name: { type: 'string', required: true },
    arrOfObjs: {
        type: 'array',
        validate: {
            rule: validateArrOfObjs,
        }
    },
    createdOn: { type: 'string', default: gstore.defaultValues.NOW, write: false, read: false }
});

function validateArrOfObjs(obj, validator) {
    if (!Array.isArray(obj)) {
        return false;
    }

    var allObjs = obj.every((item) => {
        return item !== null && typeof item === 'object';
    });

    return allObjs;
};

const itemQuerySettings = {
    order : { property: 'createdOn', descending: true }, // descending defaults to false and is optional
    select : ['name', 'createdOn']
};

// Add settings to schema
itemSchema.queries('list', itemQuerySettings)

const Item = gstore.model('Item', itemSchema);
export default Item;

If I create a few items and then try to list them with the following endpoint:

const listItems = (req, res) => {
  Item.list()
    .then((response) => {
      res.json({
        entities: response.entities,
        nextPageCursor: response.nextPageCursor
      });
    });
};

I get the following error:

(node:14987) UnhandledPromiseRejectionWarning: Error: no matching index found. recommended index is:
- kind: Item
  properties:
  - name: createdOn
    direction: desc
  - name: name

    at ./node_modules/grpc/src/client.js:554:15

On node v9.3.0, Yarn package.json:

{
  "name": "gstore-poc",
  "version": "1.0.0",
  "description": "POC for gstore library",
  "main": "index.js",
  "scripts": {
    "api": "babel-node server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "babel-preset-env": "^1.6.1",
    "body-parser": "^1.18.2",
    "express": "^4.16.2",
    "gstore-node": "^2.1.4",
    "mongoose": "^5.0.0-rc1",
    "morgan": "^1.9.0"
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-core": "^6.26.0"
  },
  "babel": {
    "presets": [
      "env"
    ]
  }
}

Is there any way to save data using insert and not upsert?

Hello!

Does all the "save" operations works as upsert(which will overwrite an entity if it already exists in Cloud Datastore) or is there any way to use insert(which requires that the entity key not already exist in Cloud Datastore)?

Thanks in advance.

Convert Entity to JSON?

Hi!

I guess this is more of a question than an issue, but is there any easy way to convert these to JSON objects that can be returned by express routes? Thank you!

Schema will not allow excludeFromIndexes for an array

const studentSchema = new Schema({
name: { joi: Joi.string().required() },
scores:{ joi: Joi.array().required(), excludeFromIndexes: true }
})

I get this error

{ Error: Exclude from indexes cannot be set on a list value
at /Users/Mike/dev/current/datastore/node_modules/grpc/src/node/src/client.js:554:15 code: 400, metadata: Metadata { _internal_repr: {} } }

filter objects inside a array

If I have a array

"test": [{"organizationName":"strge","familyName":"raju"},{"organizationName":"sand","familyName":"sand"}]

want to filter in organizationName=strge and familyName=sand and get only if both filters match in particular object not in different object of a array. #

geoPoint validation

I've been trying to query and re-save a bunch of entities lately, I noticed something that causes a little friction.

When doing data validation for an entity property which has a type of gcloud.datastore.geoPoint, it will fail, even if the structure of the date is completely correct. Such as:

{
  latitude:  37.305885314941406,
  longitude: -89.51815032958984
}

It would be great if it allowed for both of these use cases- or perhaps auto-converted the data to the correct gcloud.datastore.geoPoint object automatically if the structure is fine. As the data returned from a query is just plain and it doesn't convert back easily.

Get() returns 404 with "12345:12345" formatted key name

Similar to #1, but with getting instead. Getting an object by number ID works fine, so does a regular string name (e.g: 'testobject'). However, in my case, a string name of "12345:12345" returns 404 every time through gstore-node. Tested in gcloud works successfully.

Thank you

Typescript support

Hello,

First of all thank you for your work.
I would have liked to know if you will soon migrate to typescript?

Thank you in advance

Preferred way to transform a query result back to an entity

As far as I can see now, results from a query are the raw results from the query executed by the underlying gcloud-node library? What would be the preferred way to convert these entities back to gstore-node Model objects?

I.e my use case is the following roughly:
query(..., (entities) => { // do something with entities, update some properties entity.save(); });

Issue when calling Model.get with an array with a length of 1

If you call the Model.get method with an array consisting of only one ID, nothing seems to be returned.

For example, calling the following returns an array of 2 entities:

MyModel.get([10, 15])

However calling the following doesn't return anything:

MyModel.get([10])

Saving multiple Entities

Hi there,

I see you've got the equivalent of the datastore.save( entity ) method, which we can access in this library the other way round with entity.save(); - returning a promise or passing in a callback if we like to save a single element.

I'm wondering if there is anything which would be the equivalent of datastore.save( entities[] )

For example, in my code:

async function resaveEntities(model, cursor = null) {
  const query = model.query().limit(50);

  if (cursor) {
    query.start(cursor);
  }

  const result = await query.run();
  const response = result[0];

  log.info(`fetched ${response.entities.length} entities of ${model.entityKind}`);

  // CODE HERE TO RE-SAVE ALL THE FETCHED ENTITIES

  if (response.nextPageCursor) {
    resaveEntities(model, response.nextPageCursor);
  } else {
    log.info(`END of re-saving of [${model.entityKind}]`);
  }
}

The reason I ask is because if I'm not mistaken, batch saving large groups of entities (as I'm trying to do here, basically running through all the entities of a kind and re-saving them- in order to re-index their properties if I changed the schema) can be optimised when its done in groups instead of individually every time.

I suppose I could use the google-cloud-node library to re-save them, but would I not lose the schema then? When I look at the data that is being returned in the query, it appears that it's just a bunch of plain data- no extra schema information or even functions so I'm unsure if entity.save() would actually even work either.

I'm going to play around with it for a bit. But let me know if there is a solution I may be overlooking.

`deleteAll` can not delete more than 500 entities at at time

I get an error below when deleting a table with more than 500 entities. This is because the deleteAll function seems to be fetching the entries first and then trying to delete them in one go instead of breaking them down to a batch of 500. Workaround is do the same by the caller but that makes deleteAll function unnecessary.

cannot write more than 500 entities in a single call

Incompatibility with @google-cloud/datastore 0.5.0

As google-cloud datastore lib has changed their entity response format from
{ key: someKey, data: someData }
to
{ entityData }
in @google-cloud/datastore 0.5.0 (The change is actually in this commit ), this clearly will breaks model creation. For example, trying to fetch an entity with a key would result in the following error as entity.key in the response is no longer presence.

/MyProject/node_modules/gstore-node/lib/serializers/datastore.js:23

 data.id  = entity.key.path[entity.key.path.length - 1];

                      ^

TypeError: Cannot read property 'path' of undefined

May I ask whether there is any plan to support datastore 0.5.0 soon?

update() throwing errors

I have been investigating a failing test in our test suite and am struggling with a test that basically validates some params and calls Model.update(). The call to Model.update() ends up throwing an unhandled rejection:

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property '0' of undefined

After some debugging I found the error being thrown at model.js:258 (onEntityUpdated), where updateData is undefined. This sort of makes sense as model.save() doesn't return the created entity, but this seems like a common use case and I figured the tests would cover a case like this.

I made a bare test that has this issue. DevApp has just 2 fields: appName: string, type: number:

const newName = 'New Test App Name';
const fApp = new DevApp({appName: 'f-app', type: 0});

return fApp.save(function (err) {
  assert.ifError(err);
  assert.equal(fApp.appName, 'f-app');
  return DevApp.update(fApp.entityKey.id, {appName: newName})
    .then((retApp) => {
      assert.equal(retApp.appname, newName);
      done();
    });

This is a bit contrived as I couldn't get a sane, promise-only version working either:

return fApp.save()
  .then(() => {
    return DevApp.update(fApp.entityKey.id, {appName: newName});
  })
  .then((retApp) => {
    assert.equal(retApp.appname, newName);
    done();
  })
  .catch(console.error);

This errors with: { code: 400, metadata: Metadata { _internal_repr: {} } }

I would like to be able to save a model, update it, and see the changes updated (all in a promise chain).

Am I doing something unreasonable or wrong? Any help would be appreciated.

Validate for "required:true"

Seems like it should validate for "required: true" in the schema. validate.js doesn't accept nulls or zero-lengths so maybe those checks should be at the top of model.validate() and flip skip if value is null or zero-length after trimming.

Custom Promise library

It would be awesome to be able to plug in a custom Promise library (i.e. bluebird), just like you would with mongoose.

I know you can set a custom lib for google-cloud/datastore, but it would be even cooler to have this feature for gstore-node

Cannot read property 'createQuery' of undefined

As a user of mongoose, wanting to start switching to google cloud datastore, this library is ideal for me, thank you so much for making it.

So I created a connection:

var gstore = require('gstore-node');
var gcloud = require('google-cloud')(config.googlecloud);
gstore.connect(gcloud.datastore());

Then I created a schema and made it into a model:

var gstore = require('gstore-node');
var Schema = gstore.Schema;

var urlSchema= new Schema({
    name: { type: 'String', excludeFromIndexes: true },
    subdomain: { type: 'String' }
});

module.exports = gstore.model('url', urlSchema);

I don't know what step I might be missing in the docs, but when I try to query my new model:

var Url = require('./url.model');
Url.query().run(function (error, results) {
    console.error(error);
    console.log(results);
});

I get the above error (it doesn't even make it to the console.error line. Specifically, it points to line 924 of model.js in gstore-node:

return self.ds.createQuery.apply(self.ds, createQueryArgs);

For some reason, the datastore is supposed to be passed in when creating the model, but is not. Given that there is a line 924 of model.js in the lib/ folder or gstore-node, at this point I'm pretty sure I'm in over my head and could use some guidance.

Did I do something wrong in the process of creating my model? Note that if I dump the Url object, it has a ds property with a datastore. object on it. In fact, if I dump the self object in model.js, It contains the Url complete with the attached datastore, but for some reason self.ds is undefined.

Docs link broken

The line:

For info on how to configure gcloud read the docs here.

has a broken link, thanks!

Problem with the method save() after updating Node.js to version 6

I have upgraded my Node.js from 4.x.x to 6.11.0 and got a problem now. The entity method save() doesn't seem to work for me anymore. The code below throws this message in console: (node:7184) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'then' of undefined.

const faker = require('faker');
const User = require('./../models/user.model');
const entity = new User({
    displayName: faker.name.firstName() + ' ' + faker.name.lastName(),
    username: faker.internet.userName()
});

entity.save()
    .then(() => {
        res.send('OK');
    })
    .catch((err) => {
        res.send('Not OK');
    })
;

Looks like the method is not returning a promise, so the entity doesn't get saved in the datastore. On the other side if I use the general gstore method for saving, it works perfectly.

...

var gstore = require('gstore-node');

gstore.save(entity)
    .then(() => {
        res.send('OK');
    })
    .catch((err) => {
        res.send('Not OK');
    })
;

Is there a problem with my code or the library needs to be fixed? Thanks.

query on this package

Im playing around with GAE, datastore and came across this package. Taking this along lines of mongoose is really good.

One of the major complaints of GAE datastore seems to be you will be locked into google-eco-system and cant migrate to other database engines.

So what do you think about having a 'new or this' package which will act as an abstraction layer on top of datastore, mongoose and mysql. One package - talk to mongoose or datastore or mysql

This means an application 'migration' is easy and more users.

I havent used mongoose or datastore intensively - so wanted to know what you feel about this proposal ?

Question: Is there an example on how to do referenceProperty?

I am new to both datastore and gstore-node. Having a hard time figuring out how to write the schema for one-to-many and many-to-many relationships without using hierarchal relationships.

If we take a small example of a User having many Addresses, how should the schema look ?

const userSchema = new Schema({
    name: { type: 'string' }
});
const addressSchema = new Schema({
    city: { type: 'string' }
});

I apologize for posting a question here.

Array of valid values is using first value as default

Hello. I have the following schema for a Pose model:

{
  name: {type: 'string', required: true},
  skeleton: {values: ['HumanBody', 'HumanHand'], required: true},
  ...
}

When I try to create a new entity without skeleton attribute, it's automatically picking HumanBody as the default value:

console.log(new Pose({name: 'Whatever'}))
// will output: {name: 'Whatever', skeleton: 'HumanBody'}

Thus, the required: true option has no effect because it's setting a default value.

This behavior is confusing, and also undesired in my case, as I need the attribute to be explicitly defined, raising a ValidationError when not given. How can I achieve this?

Thanks.

gcloud-node v0.37.0 Transaction

With the new gcloud-node update, they changed the whole way transactions are handled.

This means the runInTransaction alias is no longer valid, and the internal transaction handling should be updated to support gcloud v0.37.0

Updating a child with object type

hello, i have an entity with the ff model.
model
first_name: { type: 'string' },
last_name: { type: 'string' },
address: { type: 'object' },

and as I save, I save the address as
{ number: 001, street: example street, City: example City }

AFTER I SAVE IT, i will update the City only...

how should i do that? because as i update it, it will overwrite all the values with
street: { City: overwriteValuehere }

i want it instead street: { number: 001, street: example street, City: overwriteValuehere }

tnx!

New entity string ID being stored as integer

For one of my kinds, I store custom string as the ID in the format of "user1:user2" (where user1 and user2 are numerical IDs) for easy and fast retrieving. To create this string, I'm using util.format and passing it to the second parameter of a new model instance. When I save this to the datastore and check to see how it saved, it shows only "user1" as a numerical ID. How can I force gstore to save the ID as a string even though one is passed to the new instance?

For now, I'm saving through gcloud which works fine.

UnhandledPromiseRejectionWarning on model.get() when a key doesn't exist

When giving a non-existent key to model.get(), I get

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'Symbol(KEY)' of undefined

and I cannot catch the rejection of Promise, like

User.get('NON-EXISTENT-KEY')
.then((entities) => {
  // an entity found
})
.catch((err) => {
  // never happen
})

I am using gstore-node 0.8.0, google-cloud 0.44.2 and node v6.9.1. I guess something happening here?
https://github.com/sebelga/gstore-node/blob/master/lib/model.js#L110

Slow loading

Sorry to be blowing up your issues list but when I launch my app with these two lines:

var gcloud = require('gcloud')(configGcloud);
var datastore = gcloud.datastore({"apiEndpoint": "127.0.0.1:8343"});

It loads in about 2 seconds, if I add:

var gstore = require('gstore-node');

it goes up to 10-15 seconds, any idea why it takes so long to import or what I'm doing wrong? Thanks!

excludeFromIndexes doesn't work

Hi, would like to know if anyone face this issue? All my properties default to Indexed even after I set excludeFromIndexes to true. Below is my schema and datastore gui.

screen shot 2017-12-06 at 4 20 26 pm

screen shot 2017-12-06 at 4 21 10 pm

Only allows simple validation rules

Hi @sebelga,

First, thanks for this beautiful project. Eases the implementation by 400%. You're awesome, keep up the good work (and have a Happy New Year πŸŽ‰).

What's the issue:

I read that you use the validator.js - we can even see it in action in this line - but that implementation only supports simple validation rules that don't expect additional parameters (e.g. the isIn rule).

How could it be solved:

I suggest either to pass something extra on the object like:

{
  validate: 'isIn',
  validation_args: [ /* an array of multiple args */ ],
  // ...
}

or change the validate prop to support both string, object/array:

// simple case
{
  validate: 'isEmail'
}

// complex case
{
  validate: {
    rule: 'isIn',
    args: [ /* ... */ ]
}

// another approach
{
  validate: ['isIn', arg2, arg3, arg4, ..., argN]
}

Or just say, "No we only support simple cases" :)

Unable to create two gstore instances bound to two separate namespaces

STR

const gstore = require('gstore-node');
const datastore1 = require('@google-cloud/datastore')({
    projectId: 'my-google-project-id',
   namespace: `1`
});

// Then connect gstore to the datastore
gstore.connect(datastore1);

const datastore2 = require('@google-cloud/datastore')({
    projectId: 'my-google-project-id',
   namespace: `2`
});
gstore.connect(datastore2);

The second datastore datastore2 overwrites the first one and therefore calls to delete operation, for example, on datastore1 are done on namespace2 instead and therefore fails. The issue is that we export new Gstore at https://github.com/sebelga/gstore-node/blob/master/lib/index.js#L135 instead of ust Gstore so there is no way to create two separate instances of Gstore

Select in a query fails when two properties are used

By running the following code:

      db
          .ProjectModel
          .query()
          .select(['name', 'updateOn']);

You get the following error

Error: no matching index found. recommended index is:<br/>- kind: Project<br/>  properties:<br/>  - name: name<br/>  - name: updateOn<br/>
    at /Users/cristyansepulveda/visualive_website/node_modules/grpc/src/node/src/client.js:569:15 code: 412, metadata: Metadata { _internal_repr: {} } }

model:

const ProjectSchema = new Schema({
    name:     {type: 'string'},
    updateOn: {type: 'datetime', default: gstore.defaultValues.NOW},
  });

Not possible to get an entity with id "1"

Because of this function:

function parseId(id) {
    return isFinite(id) ? parseInt(id, 10) : id;
}

The call User.get("1") tries to find an entity with a numeric ID instead of a String ID.
This is great for a codebase written from scratch, but causes problems when dealing with legacy.

Request: Add example to documentation for querying based on a array of ids

It had taken me while to figure out how to get it working and was thinking a example with returning objects they can use would be very helpful to new comers. In the Gitbook it is on the models/methods/GET documentation, but I think that adding a example to map out the entities and use plain() would be a very common requirement for people. It would also be good to add it to the readme where the other function calls are. Me a year ago probably would have never figured it out :)

My function for fetching based on a array of _id's and returning objects
If there is a more performant way to do this please let me know!

export async function getPagesBy_id(_ids) {
  let response = null;

  try {
    response = await Page.get(_ids, null, null, null, { preserveOrder: true })
      .then((entities) => {
        return entities.map((entity)=> {
          return entity.plain();
        });
    });

  } catch (err) {
    console.log('getPagesBy_id err', err);
  }

  return response;
};

Validation failure on save only returns first error

Hello,

while looking at the code to investigate why Gstore was returning only the first error as opposed to all of them (like ORMs normally do), I found this piece of code inside validateEntityData() in model.js:

                if (!validate.success) {
                    delete validate.success;
                    return validate.errors[Object.keys(validate.errors)[0]];
                }

As a result, Model.save() on validation failure returns only the first error.

Is there a reason for that? Couldn't we just return validate.errors in its entirety?

Support for named keys

Are there any plans to add support for named keys? It seems that createKey is always expecting ID to either be a numeric string or number, but I would like to be able to create an entity that builds keys using the string without parsing it. It would be nice to define the type of key when defining a schema.

On a related note, since the ID is being parsed with parseInt, IDs that are larger than 253 -1 will not parse properly 😒 . This could be a problem for anyone looking to interoperate with IDs generated from languages that properly support 64-bit integers. Instead of parsing as JS numbers, they should be wrapped with datastore.int().

Cannot read property 'key' of undefined when trying to add to Datastore

First off sweet project cant wait to get it working!

I am trying to get the minimal setup working and when I run my seed function to add a page to Datastore I get: cannot read property 'key' of undefined.

I have tried every different variation I could from going through the docs, and not sure if there a way I can define the key on a field in the model.

My create function
Note

  • that I am passing a object directly to it so i changed req.body to req.
  • The id I want to use is _id field on my model(I have this working without the use of gstore-node). I also chose to use _id because I would have naming conflict with my frontend relay/graphql
  • Console.log does output the correct _id string of entityData._id
/* eslint no-unused-vars: 0 */
const gstore = require('gstore-node');
const Page = require('../../../models/datastore/pageSchema');

const createPage = (req, res, next) => {
  const entityData = Page.sanitize(req);
  const page = new Page(entityData, entityData._id);

  page.save()
    .then(entity => res.json(entity.plain()))
    .catch(err => next(err));
};

And the full error:

H:\Coding\Projects\react-starter-kit\node_modules\gstore-node\lib\entity.js:158
    return namespace ? self.gstore.ds.key({ namespace, path }) : self.gstore.ds.key(path);
                                                                               ^

TypeError: Cannot read property 'key' of undefined
    at createKey (H:\Coding\Projects\react-starter-kit\node_modules\gstore-node\lib\entity.js:158:80)
    at ModelInstance.Entity (H:\Coding\Projects\react-starter-kit\node_modules\gstore-node\lib\entity.js:25:30)
    at Model (H:\Coding\Projects\react-starter-kit\node_modules\gstore-node\lib\model.js:21:1)
    at ModelInstance (H:\Coding\Projects\react-starter-kit\node_modules\gstore-node\lib\model.js:23:31)
    at createPage (H:/Coding/Projects/react-starter-kit/src/data/queries/googleDatastore/page/controller.js:23:19)
    at Object.<anonymous> (H:/Coding/Projects/react-starter-kit/src/data/seed/index.js:20:1)
    at Module._compile (module.js:570:32)
    at loader (H:\Coding\Projects\react-starter-kit\node_modules\babel-register\lib\node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .js] (H:\Coding\Projects\react-starter-kit\node_modules\babel-register\lib\node.js:154:7)
    at Module.load (module.js:487:32)
error Command failed with exit code 1.

My model

const gstore = require('gstore-node');

const Schema = gstore.Schema;

const pageSchema = new Schema({
  _id: {
    type: 'string',
    required: true
  },
  path: {
    type: 'string',
    optional: true
  },
  public: {
    type: 'boolean',
    required: true,
    default: false,
    excludeFromIndexes: true
  },
  ... rest of fields
});

const listSettings = {
  limit: 25,
  order: { property: '_id' },
};

pageSchema.queries('list', listSettings);

module.exports = gstore.model('Page', pageSchema);

Array of objects converted to string with custom validator

I'm trying to use a custom validator to verify that a field is an array containing one or more objects. However, the custom validator function is getting passed a string as input instead of the original array being passed in. It seems as if the array is having .toString() called on it before it's passed into the validator. However, if I remove the custom validator function then the correct values are saved to the database.

Code, output, package, and node version information below.

Schema and validator:

const itemSchema = new Schema({
    name: { type: 'string', required: true },
    arrOfObjs: {
        type: 'array',
        validate: {
            rule: validateArrOfObjs,
        }
    },
    createdOn: { type: 'string', default: gstore.defaultValues.NOW, write: false, read: false }
});

function validateArrOfObjs(obj, validator) {
    console.log("Input type:");
    console.log(typeof obj);
    console.log("Input is array:");
    console.log(Array.isArray(obj));
    console.log("Array:");
    console.log(obj);
    console.log("First item:");
    console.log(obj[0]);
    console.log("First item type:");
    console.log(typeof obj[0]);

    if (!Array.isArray(obj)) {
        return false;
    }

    var allObjs = obj.every((item) => {
        return item !== null && typeof item === 'object';
    });

    return allObjs;
};

const Item = gstore.model('Item', itemSchema);

Testing code:

var arr = [
  { name: 'foo' },
  { name: 'bar' },
  { name: 'baz' }
];

var item = new Item( {name: 'test123', arrOfObjs: arr} );

console.log("Array:");
console.log(item.arrOfObjs);
console.log("Array first item:");
console.log(item.arrOfObjs[0]);
console.log("Array first item type:");
console.log(typeof item.arrOfObjs[0]);

console.log("Validating!");
const { error } = item.validate();
if (error !== null) throw error;

item.save();

Output:

Array:
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
Array first item:
{ name: 'foo' }
Array first item type:
object
Validating!
Input type:
string
Input is array:
false
Array:
[object Object],[object Object],[object Object]
First item:
[
First item type:
string

yarn package.json

{
  "name": "gstore-poc",
  "version": "1.0.0",
  "description": "POC for gstore library",
  "main": "index.js",
  "scripts": {
    "api": "babel-node server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "babel-preset-env": "^1.6.1",
    "body-parser": "^1.18.2",
    "express": "^4.16.2",
    "gstore-node": "^2.1.2",
    "mongoose": "^5.0.0-rc1",
    "morgan": "^1.9.0"
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-core": "^6.26.0"
  },
  "babel": {
    "presets": [
      "env"
    ]
  }
}

Node version:

node --version
v9.3.0

[help-wanted] Problem with save method: Saving doesn't do anything

Hi,
I'm having some issues working with the library. Maybe I'm initializing in a bad way the gstore-node?

Saving is not working, or nothing happens at all when this method is executed:

    var data = ClientApp.sanitize(req.body);
    console.log(data);
    var clientApp = new ClientApp(data,data.appName);

    clientApp.save().then(function() {
        this.datastoreEntity().then((data) => {
            var entity = data[0];
            console.log(entity);
        });
    }).catch((err) => {
        console.log(err);
    });

Here are the files:
clientApp.controller.js

var ClientApp = require('../models/clientApp');

function create(req, res) {
    var data = ClientApp.sanitize(req.body);
    console.log(data);

    // {
    //     appName: 'Yikes',
    //     androidBundleId: 't.t.t',
    //     iosBundleId: 'a.a.a'
    // }

    var clientApp = new ClientApp(data, data.appName);

    var data = ClientApp.sanitize(req.body);
    console.log(data);
    var clientApp = new ClientApp(data,data.appName);

    clientApp.save().then(function() {
        this.datastoreEntity().then((data) => {
            var entity = data[0];
            console.log(entity);
        });
    }).catch((err) => {
        console.log(err);
    });
}


module.exports = {
    create: create
};

(Model)
clientApp.js

var uuid = require('uuid');

var datastore = require('@google-cloud/datastore')({
    projectId: 'tttttttt',
    keyFilename: './keyfile.json'
});

var gstore = require('gstore-node');
gstore.connect(datastore);

var Schema = gstore.Schema;

var schema = new Schema({
    appName: {
        type: 'string',
        required: true,
        read: false
    },
    androidBundleId: {
        type: 'string',
        required: true,
        read: false
    },
    iosBundleId: {
        type: 'string',
        required: true,
        read: false
    },
    modified: {
        type: 'boolean',
        default: false,
        read: false,
        write: false
    },
    createdOn: {
        type: 'datetime',
        default: gstore.defaultValues.NOW,
        write: false,
        read: false
    },
    appKey: {
        type: 'string',
        default: uuid.v4(),
        write: false
    }
});

schema.virtual('timestamp').get(function() {
    // the scope (this) is the entityData object
    return this.createdOn;
});

var ClientApp = gstore.model('ClientApp', schema);

module.exports = ClientApp;

Possible bug with array properties showing indexed (but the contents show excludedFromIndexes

Can't find any effect of this. Model shows excludeFromIndexes
image

The field shows that it is indexed. (No other properties falsely show it)
image

But then its values all state not indexed. (Note the array content type)
image

Not sure if its anything to pay attention to as it is not stored in the indexs (n0deEdge).
The note above showed that the content type of n0deEdge is an array (which is correct), and below it shows it is a string.
image

can't save embedded entity with string length > 1500

Datastore refuses 'string' properties greater than 1500 characters. It will accept 'text' properties of any length. How do we set the property type to 'text'?

> var Test = require('./server/apis/test.model.js')
datastore.js
shioh.productArray.model.js
undefined
> var str = ''
undefined
> for (let i = 0; i<1502; i++){str = str.concat('a')}
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'

> var entity = new Test({text : str})
undefined
> entity
ModelInstance {
  className: 'Entity',
  schema:
   Schema {
     instanceOfSchema: true,
     methods: {},
     statics: {},
     virtuals: {},
     shortcutQueries: {},
     paths: { text: {} },
     callQueue: { model: {}, entity: {} },
     options: { validateBeforeSave: true, queries: [Object] },
     __meta: {} },
  excludeFromIndexes: [],
  entityKey: Key { namespace: undefined, kind: 'test', path: [Getter] },
  entityData: { text: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaa' },
  pre: [Function: pre],
  post: [Function: post],
  hook: [Function: hook],
  __setupHooks: [Function: __setupHooks] }
> entity.save()
Promise { <pending> }
> (node:12000) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: The value of property "text" is lo
nger than 1500 bytes.

pre-hooks: how does __override work

I'm reading the gstore-node docs & I'm not clear on how the __override property on discussed here https://sebelga.gitbooks.io/gstore-node/content/middleware-hooks/pre-hooks.html works (and what is it for)?

can you elaborate & give an example?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.