-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: refactor entities #345
Conversation
Show some examples in the PR description? |
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"],
}); |
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"],
}); |
export interface Entity<E> extends Omit<EntitySpec, "schema" | "streams"> { | ||
export type AnyEntity = Entity<any, string, string | undefined>; | ||
|
||
export type EntityKeyType = string | number | EntityBinaryMember; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't suffix with Type
export type EntityKeyField<E extends EntityValue> = { | ||
[K in keyof E]: K extends string | ||
? E[K] extends EntityKeyType | ||
? K | ||
: never | ||
: never; | ||
}[keyof E]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just keyof E
or Extract<keyof E, string>
?
: never; | ||
}[keyof E]; | ||
|
||
export type EntityCompositeKeyFromEntity<E extends AnyEntity> = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should AnyEntity
be replaced with default type arguments?
partitionKey: EntityKeyReference<E, P>; | ||
sortKey?: EntityKeyReference<E, S>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these not just partition: P
? Types are a bit out of control.
Usually good practice to avoid overly using mapped types, especially when all you're doing is storing the data directly on the object.
streams: EntityStream<E>[]; | ||
partitionKey: EntityKeyReference<E, P>; | ||
sortKey?: EntityKeyReference<E, S>; | ||
schema?: ZodMappedSchema<E>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is a ZodMappedSchema?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so it constructs the schema from the value type. Shouldn't we be including the zod types in everything? Go backwards from schema?
export type ZodMappedSchema<E extends EntityValue> = { | ||
[k in keyof E]: z.Schema<E[k]>; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this type needed?
export interface EntityOptions< | ||
E extends EntityValue, | ||
P extends EntityKeyField<E>, | ||
S extends EntityKeyField<E> | undefined | ||
> { | ||
partitionKey: EntityKeyReference<E, P>; | ||
sortKey?: EntityKeyReference<E, S>; | ||
schema: ZodMappedSchema<E>; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type is surprisingly complex.
export interface EntityOptions<
A extends EntityAttributes | z.ZodObject<any>,
P extends keyof E | (keyof E)[],
S extends keyof E | (keyof E)[] | undefined = undefined
> {
partitionKey: P;
sortKey?: S;
attributes: A
}
> { | ||
partitionKey: EntityKeyReference<E, P>; | ||
sortKey?: EntityKeyReference<E, S>; | ||
schema: ZodMappedSchema<E>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be called attributes
?
* | ||
* @default - retrieve values with no namespace. | ||
*/ | ||
namespace?: string; | ||
partition: E[Partition]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it only accepts a single value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have not pushed a commit since the last time we talked. Should be ready soon.
export type EntityMethod = Exclude< | ||
{ | ||
[k in keyof AnyEntity]: [AnyEntity[k]] extends [Function] ? k : never; | ||
}[keyof AnyEntity], | ||
"partitionKey" | "sortKey" | "stream" | "__entityBrand" | undefined | ||
>; | ||
|
||
/** | ||
* Registers and returns functioning {@link Entity}s. | ||
* | ||
* Does not handle the workflow case. That is handled by the {@link entity} function in core. | ||
*/ | ||
export interface EntityHook { | ||
getEntity<Entity>(name: string): Promise<EntityMethods<Entity> | undefined>; | ||
export type EntityHook = { | ||
[K in EntityMethod]: ( | ||
entityName: string, | ||
...args: Parameters<AnyEntity[K]> | ||
) => ReturnType<AnyEntity[K]>; | ||
} & { | ||
transactWrite(items: EntityTransactItem<any>[]): Promise<void>; | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need these types? It's hard to follow
export type EntityQueryKey< | ||
Attr extends EntityAttributes = EntityAttributes, | ||
Partition extends EntityCompositeKeyPart<Attr> = EntityCompositeKeyPart<Attr>, | ||
Sort extends EntityCompositeKeyPart<Attr> | undefined = | ||
| EntityCompositeKeyPart<Attr> | ||
| undefined | ||
> = Partial<EntityCompositeKey<Attr, Partition, Sort>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export type EntityQueryKey<
Attr extends EntityAttributes = EntityAttributes,
Partition extends EntityCompositeKeyPart<Attr> = EntityCompositeKeyPart<Attr>,
Sort extends EntityCompositeKeyPart<Attr> | undefined =
| EntityCompositeKeyPart<Attr>
| undefined
> = {
[pk in Partition[number]]: string;
} & (Sort extends undefined
? {}
: ProgressiveQueryKey<Attr, Exclude<Sort, undefined>>);
type ProgressiveQueryKey<
Attr extends EntityAttributes,
Sort extends readonly (keyof Attr)[],
Accum extends object = {}
> = Sort extends []
? Accum
: Sort extends [
infer k extends keyof Attr,
...infer ks extends (keyof Attr)[]
]
?
| (Accum & {
[sk in k]: Attr[sk];
})
| ProgressiveQueryKey<
Attr,
ks,
Accum & {
[sk in Sort[0]]: Attr[sk];
}
>
: never;
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:
[partition, sort]
- Delete/Get{ [partitionField]: value, [sortField]?: value }
- Delete/Get{ [partitionField]: value, [sortField]?: value }
- SetWhen 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