-
Notifications
You must be signed in to change notification settings - Fork 3
Partial Key Queries #33
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import { Table } from "./types" | ||
| import { buildKeyArray } from "./util" | ||
|
|
||
| export function generateTables(tables: Table[]): string { | ||
| const tableCode = tables.map((table) => { | ||
|
|
@@ -24,13 +25,18 @@ function generateEncryptionBlacklist(table: Table): string { | |
| .forEach((model) => { | ||
| const [, pk] = model.keys.partitionKey | ||
| const [, sk] = model.keys.sortKey | ||
| encryptionBlacklistSet.add(pk.replace("$", "")) | ||
| encryptionBlacklistSet.add(sk.replace("$", "")) | ||
|
|
||
| const pks = buildKeyArray(model.keys.partitionKey[1]) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like you can just use the |
||
| const sks = buildKeyArray(model.keys.sortKey[1]) | ||
| pks.forEach((key: string) => encryptionBlacklistSet.add(key)) | ||
| sks.forEach((key: string) => encryptionBlacklistSet.add(key)) | ||
| }) | ||
|
|
||
| table.gsis.forEach(({ name, partitionKey, sortKey }) => { | ||
| encryptionBlacklistSet.add(partitionKey.replace("$", "")) | ||
| encryptionBlacklistSet.add(sortKey.replace("$", "")) | ||
| const pk = buildKeyArray(partitionKey) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: would just inline to: |
||
| const sk = buildKeyArray(sortKey) | ||
| pk.forEach((key: string) => encryptionBlacklistSet.add(key)) | ||
| sk.forEach((key: string) => encryptionBlacklistSet.add(key)) | ||
| }) | ||
|
|
||
| return JSON.stringify(Array.from(encryptionBlacklistSet)) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,3 +11,27 @@ export function groupBy<T extends { [key: string]: any }, U extends keyof T>( | |
|
|
||
| return Object.entries(groupedItems) | ||
| } | ||
|
|
||
| export function replaceKeyDollar(key: string | string[]) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm...we're threading this
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Style: all For inline lambdas, e.g. |
||
| if (Array.isArray(key)) { | ||
| return key.map((k) => k.replace("$", "")) | ||
| } | ||
| return key.replace("$", "") | ||
| } | ||
|
|
||
| export function buildKey(key: string | string[]) { | ||
| const cleanKey = replaceKeyDollar(key); | ||
| if (Array.isArray(cleanKey)) { | ||
| const keys = cleanKey.map((k) => `"${k}"`).join(', ') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not clear on why we put a dollar sign on each key part, and then just remove the first one below. |
||
| return `[${keys}]` | ||
| } | ||
| return `"${cleanKey.replace("$", "")}"` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to do what your |
||
| } | ||
|
|
||
| export function buildKeyArray(key: string | string[]) { | ||
| const keyOrKeys = replaceKeyDollar(key) | ||
| if (Array.isArray(keyOrKeys)) { | ||
| return keyOrKeys | ||
| } | ||
| return [keyOrKeys] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,21 @@ | ||
| import { PartitionAndSortKey, PartitionKeyAndSortKeyPrefix } from "./keys" | ||
| import { Table } from "./Table" | ||
| import { TaggedModel } from "./types" | ||
| import { TaggedModel, AtLeastOne } from "./types" | ||
| import { key } from "aws-sdk/clients/signer" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't think you intended to import this? |
||
|
|
||
| export class Model< | ||
| T extends TaggedModel, | ||
| U extends keyof T, | ||
| V extends keyof T | ||
| > { | ||
| > { | ||
| constructor( | ||
| private table: Table, | ||
| private partitionKeyPrefix: string, | ||
| private partitionKeyField: U, | ||
| private partitionKeyField: U | U[], | ||
| private sortKeyPrefix: string, | ||
| private sortKeyField: V, | ||
| private sortKeyField: V | V[], | ||
| readonly modelTag: string | ||
| ) {} | ||
| ) { } | ||
|
|
||
| key( | ||
| params: { [X in U]: string } & { [Y in V]: string } | ||
|
|
@@ -27,10 +28,36 @@ export class Model< | |
| } = this | ||
| return new PartitionAndSortKey( | ||
| this.table.partitionKeyName, | ||
| this.buildKey(partitionKeyPrefix, params[partitionKeyField]), | ||
| this.buildKey( | ||
| partitionKeyPrefix, | ||
| this._buildPartitionKey(params, partitionKeyField)), | ||
|
|
||
| this.table.sortKeyName, | ||
| this.buildKey(sortKeyPrefix, params[sortKeyField]), | ||
| this.buildKey( | ||
| sortKeyPrefix, | ||
| this._buildSortKey(params, sortKeyField)), | ||
| this.modelTag | ||
| ) | ||
| } | ||
|
|
||
| partialKey( | ||
| params: AtLeastOne<{ [X in U]: string }> & AtLeastOne<{ [Y in V]: string }> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is where I think I could possibly use a generated type of only valid combinations of keys.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... I think the types we'd want to express here would be a union like:
This is tricky to do as is since:
This would be easier to express if we explicitly codegenned each model vs having a generic model class as we do today. Since then, we could just codegen the right types directly |
||
| ): PartitionKeyAndSortKeyPrefix<T> { | ||
| const { | ||
| partitionKeyPrefix, | ||
| sortKeyPrefix, | ||
| partitionKeyField, | ||
| sortKeyField, | ||
| } = this | ||
| return new PartitionKeyAndSortKeyPrefix( | ||
| this.table.partitionKeyName, | ||
| this.buildKey( | ||
| partitionKeyPrefix, | ||
| this._buildPartitionKey(params, partitionKeyField)), | ||
| this.table.sortKeyName, | ||
| this.buildKey( | ||
| sortKeyPrefix, | ||
| this._buildSortKey(params, sortKeyField)), | ||
| this.modelTag | ||
| ) | ||
| } | ||
|
|
@@ -39,7 +66,9 @@ export class Model< | |
| const { partitionKeyPrefix, partitionKeyField } = this | ||
| return new PartitionKeyAndSortKeyPrefix( | ||
| this.table.partitionKeyName, | ||
| this.buildKey(partitionKeyPrefix, params[partitionKeyField]), | ||
| this.buildKey( | ||
| partitionKeyPrefix, | ||
| this._buildPartitionKey(params, partitionKeyField)), | ||
| this.table.sortKeyName, | ||
| this.sortKeyPrefix, | ||
| this.modelTag | ||
|
|
@@ -59,9 +88,10 @@ export class Model< | |
|
|
||
| const pk = this.buildKey( | ||
| partitionKeyPrefix, | ||
| fieldsWithTag[partitionKeyField] | ||
| ) | ||
| const sk = this.buildKey(sortKeyPrefix, fieldsWithTag[sortKeyField]) | ||
| this._buildPartitionKey(fieldsWithTag, partitionKeyField)) | ||
| const sk = this.buildKey( | ||
| sortKeyPrefix, | ||
| this._buildSortKey(fieldsWithTag, sortKeyField)) | ||
|
|
||
| return { | ||
| ...fieldsWithTag, | ||
|
|
@@ -70,13 +100,49 @@ export class Model< | |
| } | ||
| } | ||
|
|
||
| private buildKey(prefix: string, key: string): string { | ||
| /* TODO: Had issue mapping unioned fields of T & V */ | ||
| private _buildPartitionKey(model: AtLeastOne<{ [X in U]: string }>, fields: U | U[]): string | string[] { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| if (Array.isArray(fields)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is another place we're we'd benefit from normalizing non-array keys into arrays. For example, we could transform keys to arrays internally in the model's constructor. |
||
| const pkey: string[] = []; | ||
| for (const field of fields) { | ||
| const value = model[field] | ||
| if (value) { | ||
| pkey.push(value) | ||
| } else { | ||
| break | ||
| } | ||
| }; | ||
| return pkey | ||
| } | ||
| return model[fields as U] | ||
| } | ||
|
|
||
| private _buildSortKey(model: { [Y in V]: string }, fields: V | V[]): string | string[] { | ||
| if (Array.isArray(fields)) { | ||
| const skey: string[] = []; | ||
| for (const field of fields) { | ||
| const value = model[field] | ||
| if (value) { | ||
| skey.push(value) | ||
| } else { | ||
| break | ||
| } | ||
| }; | ||
| return skey | ||
| } | ||
| return model[fields as V] | ||
| } | ||
|
|
||
| private buildKey(prefix: string, key: string | string[]): string { | ||
| if (Array.isArray(key)) { | ||
| return `${prefix}-${key.join('-')}` | ||
| } | ||
| return `${prefix}-${key}` | ||
| } | ||
| } | ||
|
|
||
| export class PartitionKeyBuilder<T extends TaggedModel> { | ||
| constructor(private table: Table, private modelTag: string) {} | ||
| constructor(private table: Table, private modelTag: string) { } | ||
| partitionKey<U extends keyof T>( | ||
| prefix: string, | ||
| partitionKeyField: U | ||
|
|
@@ -94,11 +160,11 @@ export class SortKeyBuilder<T extends TaggedModel, U extends keyof T> { | |
| constructor( | ||
| private table: Table, | ||
| private partitionKeyPrefix: string, | ||
| private partitionKeyField: U, | ||
| private partitionKeyField: U | U[], | ||
| private modelTag: string | ||
| ) {} | ||
| ) { } | ||
|
|
||
| sortKey<V extends keyof T>(prefix: string, sortKeyField: V): Model<T, U, V> { | ||
| sortKey<V extends keyof T>(prefix: string, sortKeyField: V | V[]): Model<T, U, V> { | ||
| return new Model( | ||
| this.table, | ||
| this.partitionKeyPrefix, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,3 +11,5 @@ export type ExtractKeyType<T> = T extends PartitionAndSortKey<infer U> | |
| export type GroupedModels<T extends TaggedModel> = { | ||
| [K in T["model"]]: T extends { model: K } ? T[] : never | ||
| } | ||
|
|
||
| export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... This ends up generating a pretty crazy type that's hard to read. And it appears to be basically equivalent to |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -322,3 +322,49 @@ Tables: | |
|
|
||
| expect(lines).toContainEqual(`name: BestNameEvah`) | ||
| }) | ||
|
|
||
| it("should generate a complex key model", () => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice test, thanks! |
||
| const result = generateCode(` | ||
| Tables: | ||
| ComplexLibrary: | ||
| Partitions: | ||
| ComplexAuthors: | ||
| ComplexAuthor: | ||
| partitionKey: [Author, $id] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would like to add a test for partition keys as well, since it looks like this PR supports complex keys for both. |
||
| sortKey: [Author, [$id, $name]] | ||
| id: string | ||
| name: string | ||
| `) | ||
|
|
||
| expect(result).toEqual(`import { Table } from "@ginger.io/beyonce" | ||
|
|
||
| export const ComplexLibraryTable = new Table({ | ||
| name: "ComplexLibrary", | ||
| partitionKeyName: "pk", | ||
| sortKeyName: "sk", | ||
| encryptionBlacklist: ["id", "name"] | ||
| }) | ||
|
|
||
| export enum ModelType { | ||
| ComplexAuthor = "ComplexAuthor" | ||
| } | ||
|
|
||
| export interface ComplexAuthor { | ||
| model: ModelType.ComplexAuthor | ||
| id: string | ||
| name: string | ||
| } | ||
|
|
||
| export const ComplexAuthorModel = ComplexLibraryTable.model<ComplexAuthor>( | ||
| ModelType.ComplexAuthor | ||
| ) | ||
| .partitionKey("Author", "id") | ||
| .sortKey("Author", ["id", "name"]) | ||
|
|
||
| export type Model = ComplexAuthor | ||
|
|
||
| export const ComplexAuthorsPartition = ComplexLibraryTable.partition([ | ||
| ComplexAuthorModel | ||
| ]) | ||
| `) | ||
| }) | ||
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.
These imports seem not be used?