sam-goodwin / eventual

Build scalable and durable micro-services with APIs, Messaging and Workflows
https://docs.eventual.ai
MIT License
174 stars 4 forks source link

feat: refactor entities #345

Closed thantos closed 1 year ago

thantos commented 1 year ago

Entities now support a more classical entity pattern, more in line with Dynamo where the partition key and optional sort key must be provided at creation time.

The key is a composite key provided in the following ways:

When a key is returned, we always use the Map format (or the in the value format for get).

Caveat 1: the schema is now required as I was struggling to get the types to work with just the type parameters. Caveat 2: existing tables must be renamed and recreated

const myEntity = entity("myEntity", { 
   schema: { key: z.string(), value: z.number() },
   partitionKey: "key" });
const myEntity2 = entity("myEntity2", { 
   schema: { part: z.string(), key: z.string(), value: z.number() }, 
   partitionKey: "part", 
   sortKey: "key"});
// to use numbers or binary as keys, provide the type explictly
const myEntity3 = entity("myEntity3", { 
   schema: { key: z.number(), value: z.number() }, 
   partitionKey: { key: "key", type: "number" }});

const { version } = await myEntity.set({ key: "aValue", value: 1 });
await myEntity.set({ key: "aValue", value: 1 }, { expectedVersion: version  });
await myEntity.get({ key: "aValue" });
await myEntity.get(["aValue"]);
await myEntity.delete({ key: "aValue" });

await myEntity.query({ partition: "aValue" }); // without a sort key, only one value is returned.
sam-goodwin commented 1 year ago

Show some examples in the PR description?

sam-goodwin commented 1 year ago

Not sure what's going on with the way we configure partition/sort keys. Should be an array of field names

const myEntity3 = entity("myEntity3", { 
  attributes: { 
    key: z.number(),
    value: z.number(),
    createTime: z.date()
  }, 
  partition: ["key"],
  sort: ["value", "createTime"]
});

await myEntity3.query({
  key: "value",
  value: 1,
  createdTime: {
    startsWith: "2024-01-01T00:00Z",
    between: ["2024", "2025"]
  }
});

const myIndex = myEntity3.index({
  partition: ["key"],
  sort: ["value", "createTime"],
});
sam-goodwin commented 1 year ago

Some help with the types for capturing order of PK/SK

type Shape = {
  [attribute in string]: z.ZodType;
};

type Attributes = z.ZodObject<any> | Shape;

type AttributeNames<Attr extends Attributes> = Attr extends z.ZodObject<infer A>
  ? keyof A
  : keyof Attr;

export interface Entity2<
  Attr extends z.ZodObject<any> | Shape,
  Partition extends readonly AttributeNames<Attr>[],
  Sort extends readonly AttributeNames<Attr>[] | undefined = undefined
> {}

export function entity2<
  const Attr extends z.ZodObject<any> | Shape,
  const Partition extends readonly AttributeNames<Attr>[],
  const Sort extends readonly AttributeNames<Attr>[] | undefined = undefined
>(input: {
  attributes: Attr;
  partition: Partition;
  sort?: Sort;
}): Entity2<Attr, Partition, Sort> {
  throw new Error();
}

const a = entity2({
  attributes: z.object({
    key: z.string(),
    id: z.string(),
  }),
  partition: ["key", "id"],
  // sort: ["key", "id"],
});