gstore-node icon indicating copy to clipboard operation
gstore-node copied to clipboard

Key datastore property bleeds into typescript Type

Open jacobg opened this issue 6 years ago • 11 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?

jacobg avatar Jul 19 '19 21:07 jacobg

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

sebelga avatar Sep 10 '19 06:09 sebelga

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.

jacobg avatar Sep 11 '19 21:09 jacobg

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 }>({ ... })

sebelga avatar Sep 12 '19 13:09 sebelga

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.

jacobg avatar Sep 12 '19 13:09 jacobg

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.

sebelga avatar Sep 12 '19 13:09 sebelga

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

sebelga avatar Sep 16 '19 16:09 sebelga

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.

jacobg avatar Sep 17 '19 18:09 jacobg

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!

sebelga avatar Sep 17 '19 18:09 sebelga

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?

jacobg avatar Sep 17 '19 18:09 jacobg

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.

sebelga avatar Sep 18 '19 07:09 sebelga

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.

jacobg avatar Sep 18 '19 18:09 jacobg