sebelga / gstore-node

Google Datastore Entities Modeling for Node.js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Key datastore property bleeds into typescript Type

jacobg opened this issue · comments

Following the documentation on using gstore-node with Typescript, I had to define a custom Type which has a key property in the following way:

    type ChildType = {
      // It's not great that the type needs to bleed datastore semantics.
      // The type is supposed to be datastore-agnostic.
      // Probably a string id value is more appropriate.
      parentField: entity.Key
    }
    const ChildSchema = new gstore.Schema<ChildType>({
      parentField: { type: gstore.Schema.Types.Key, ref: ParentModel.entityKind }
    })
    const ChildModel = gstore.model<ChildType>('ChildType', ChildSchema)

I had tried using a string or a long type on the parentField, but it causes a compile error when calling new ChildModel({ ... }).

It would seem that the ChildType's parentField should be database-agnostic, and maybe an int | string union type. Can't the kind and namespace be inferred from the child entity itself?

Hello,

I am not sure I am following you. Being able to declare parentField: entity.Key, when you call new ChildModel({ ... }) will allow us to get Typescript error if we don't provide a Key type. Which is what we expect.

Could you elaborate with an example of the issue? gstore is meant to be used with the Datastore, why would you need it to be database-agnostic?

Cheers

I mean just the type class. It would be useful in a common case to just have the type define the id as a string or a number, so that the type class is an abstraction. That way classes outside the data access layer don't need to have a direct dependency on the datastore library.

Would something like this work (haven't tested it)?

// Database agnostic
interface MyEntity {
  name: string;
  age: number
}

// Union with the specific for the Datastore
const schema = new gstore.Schema<MyEntity & { parentField: entity.Key }>({ ... })

But I'd like MyEntity to have a reference to the parent field, but without the entity.Key type, perhaps as a long or some opaque string-representation of the full key.

I am not sure I fully understand your goal.

The required type for the parentField property is a entity.Key. Nothing stops you to set it as any if you want to hide the implementation behind. But then, you won't get an error if you don't provide the correct type.

Sorry, I think I finally understand what you mean. You are saying that in order to set the Type we need to import in a subfolder of the google-cloud/datastore lib?

import { entity } from '@google-cloud/datastore/build/src/entity';

This is indeed not good and I will open a PR on Google repo when I finish converting the code to Typescript

There's the dependency issue, and there's also just having to use the datastore library's entity key in a type, where it would be nice to just use a string or number, and have the schema convert it to the native type.

Can you detail how exactly you would see that?

So you will be providing an entity Key when saving (an object with an id, a namespace and possible ancestors). How is Typescript going to give you validation error if you type it as number?

The Type you provide to your schema is a generic Typescript that is being forwarded and consumed later.

const addressKey = AddressModel.key('abc'); // This is typed as an entity.Key

// Here you can't pass a number or TS will complain 
const user = new User({ address: addressKey }); 

Sorry, I would need to see an example of code to understand how you would do it. Thanks!

Sure!

Here's an example set of schemas:

  const OrganizationSchema = new gstore.Schema<types.Organization>({
    name: { type: String, required: true }
  })

  const OrganizationModel = gstore.model<types.Organization>(
    'Organization', OrganizationSchema)

  const EmployeeSchema = new gstore.Schema<types.Employee>({
    name: { type: String, required: true },
    organization: { type: gstore.Schema.Types.Key, ref: OrganizationModel.entityKind, required: true }
  })

For this Employee type, I have to declare:

  export type Employee = {
    name: string
    organization: entity.Key
  }

Whereas I'd prefer to declare my type like this:

  export type Employee = {
    name: string
    organization: string | number
  }

That way, this type can be used outside the data access layer, and no other parts of the application need to know or care that the data access layer uses Cloud Datastore.

There still may need to be an extra type declared that gets joined with each specific type which includes the primary key, but it can be database agnostic:

export type SavedType<T> = {id: string | number} & T;

Does that clarify?

Thanks for the example, I will look into it after I finish refactoring the code base to Typescript.

You haven't shown me how you would save a Key as organisation when creating a new Employee. string | number is not enough, there is a possible namespace and ancestor path to provide.

Thanks!

Perhaps the entity key could be converted to an opaque string, rather than an object? It's convention for most databases to have some sort of reference key, either a number or a string.