Skip to content
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

Bumps version and updates changelog #468

Merged
merged 4 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,10 @@ All notable changes to this project will be documented in this file. Breaking ch
- Updated `@aws-sdk/lib-dynamodb` dependency from pinned version `3.395.0` to latest release `^3.654.0`. This impacts users using the v3 aws-sdk.
- Adds dependency `@aws-sdk/util-dynamodb` for unmarshalling functionality.

## [2.15.1] - 2025-02-11
### Hotfix
- Fixed typing for "batchGet" where return type was not defined as a Promise in some cases. This change is the 2.0.0 hotfix, the corresponding 3.0.0 change was introduced in [3.2.0](#320).

## [3.0.0]
### Changed
- ElectroDB is changing how it generates query parameters to give more control to users. Prior to `v3`, query operations that used the `gt`, `lte`, or `between` methods would incur additional post-processing, including additional filter expressions and some sort key hacks. The post-processing was an attempt to bridge an interface gap between attribute-level considerations and key-level considerations. Checkout the GitHub issue championed by @rcoundon and @PaulJNewell77 [here](https://github.com/tywalch/electrodb/issues/228) to learn more. With `v3`, ElectroDB will not apply post-processing to queries of any type and abstains from adding implicit/erroneous filter expressions to queries _by default_. This change should provide additional control to users to achieve more advanced queries, but also introduces some additional complexity. There are many factors related to sorting and using comparison queries that are not intuitive, and the simplest way to mitigate this is by using additional [filter expressions](https://electrodb.dev/en/queries/filters/) to ensure the items returned will match expectations. To ease migration and adoption, I have added a new execution option called `compare`; To recreate `v2` functionality without further changes, use the execution option `{ compare: "v2" }`. This value is marked as deprecated and will be removed at a later date, but should allow users to safely upgrade to `v3` and experiment with the impact of this change on their existing data. The new `compare` option has other values that will continue to see support, however; to learn more about this new option, checkout [Comparison Queries](https://electrodb.dev/en/queries/query#comparison-queries).
Expand All @@ -554,7 +558,15 @@ All notable changes to this project will be documented in this file. Breaking ch

## [3.1.0]
### Fixed
- [Issue #464](https://github.com/tywalch/electrodb/issues/464); When specifing return attributes on retrieval methods, ElectroDB would unexpectly return null or missing values if the options chosen resulted in an empty object being returned. This behavor could be confused with no results being found. ElectroDB now returns the empty object in these cases.
- [Issue #464](https://github.com/tywalch/electrodb/issues/464); When specifying return attributes on retrieval methods, ElectroDB would unexpectedly return null or missing values if the options chosen resulted in an empty object being returned. This behavior could be confused with no results being found. ElectroDB now returns the empty object in these cases.

### Added
- ElectroDB Error objects no contain a `params()` method. If your operation resulted in an error thrown by the DynamoDB client, you can call the `params()` method to get the compiled parameters sent to DynamoDB. This can be helpful for debugging. Note, that if the error was thrown prior to parameter creation (validation errors, invalid query errors, etc) then the `params()` method will return the value `null`.
- ElectroDB Error objects no contain a `params()` method. If your operation resulted in an error thrown by the DynamoDB client, you can call the `params()` method to get the compiled parameters sent to DynamoDB. This can be helpful for debugging. Note, that if the error was thrown prior to parameter creation (validation errors, invalid query errors, etc) then the `params()` method will return the value `null`.

## [3.2.0]
### Fixed
- When updating an item with a map attribute, if you attempt to set multiple keys that are identical after removing non-word characters `(\w)`, Electro will generate the same expression attribute name for both keys. This occurs even though the original keys are different, leading to conflicts in the update operation. This update introduces a new change that ensures that each key will generate a unique expression attribute name. Contribution provided by [@anatolzak](https://github.com/anatolzak) via [PR #461](https://github.com/tywalch/electrodb/pull/461). Thank you for your contribution!
- Fixed typing for "batchGet" where return type was not defined as a Promise in some cases.

### Changed
- [Issue #416](https://github.com/tywalch/electrodb/issues/416); You can now use reverse indexes on keys defined with a `template`. Previously, ElectroDB would throw if your entity definition used a `pk` field as an `sk` field (and vice versa) across two indexes. This constraint has been lifted _if_ the impacted keys are defined with a `template`. Eventually I would like to allow this for indexes without the use of `template`, but until then, this change should help some users who have been impacted by this constraint.
66 changes: 33 additions & 33 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2716,23 +2716,23 @@ type GoBatchGetTerminal<
> = <Options extends GoBatchGetTerminalOptions<keyof ResponseItem>>(
options?: Options,
) => Options extends GoBatchGetTerminalOptions<infer Attr>
? "preserveBatchOrder" extends keyof Options
? Options["preserveBatchOrder"] extends true
? Promise<{
data: Array<
Resolve<
| {
[Name in keyof ResponseItem as Name extends Attr
? Name
: never]: ResponseItem[Name];
}
| null
>
>;
unprocessed: Array<
Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>
>;
}>
? "preserveBatchOrder" extends keyof Options
? Options["preserveBatchOrder"] extends true
? Promise<{
data: Array<
Resolve<
| {
[Name in keyof ResponseItem as Name extends Attr
? Name
: never]: ResponseItem[Name];
}
| null
>
>;
unprocessed: Array<
Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>
>;
}>
: Promise<{
data: Array<
Resolve<{
Expand All @@ -2758,23 +2758,23 @@ type GoBatchGetTerminal<
>;
}>
: "preserveBatchOrder" extends keyof Options
? Options["preserveBatchOrder"] extends true
? {
data: Array<Resolve<ResponseItem | null>>;
unprocessed: Array<
Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>
>;
}
: {
? Options["preserveBatchOrder"] extends true
? Promise<{
data: Array<Resolve<ResponseItem | null>>;
unprocessed: Array<
Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>
>;
}>
: Promise<{
data: Array<Resolve<ResponseItem>>;
unprocessed: Array<
Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>
>;
}>
: Promise<{
data: Array<Resolve<ResponseItem>>;
unprocessed: Array<
Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>
>;
}
: {
data: Array<Resolve<ResponseItem>>;
unprocessed: Array<Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>>;
};
unprocessed: Array<Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>>;
}>;

type GoGetTerminal<
A extends string,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "electrodb",
"version": "3.1.0",
"version": "3.2.0",
"description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb",
"main": "index.js",
"scripts": {
Expand Down
42 changes: 39 additions & 3 deletions src/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -4276,6 +4276,7 @@ class Entity {
facets: parsedPKAttributes.attributes,
isCustom: parsedPKAttributes.isCustom,
facetLabels: parsedPKAttributes.labels,
template: index.pk.template,
};
let sk = {};
let parsedSKAttributes = {};
Expand All @@ -4294,6 +4295,7 @@ class Entity {
facets: parsedSKAttributes.attributes,
isCustom: parsedSKAttributes.isCustom,
facetLabels: parsedSKAttributes.labels,
template: index.sk.template,
};
facets.fields.push(sk.field);
}
Expand Down Expand Up @@ -4409,10 +4411,12 @@ class Entity {
const definition = Object.values(facets.byField[pk.field]).find(
(definition) => definition.index !== indexName,
);

const definitionsMatch = validations.stringArrayMatch(
pk.facets,
definition.facets,
);

if (!definitionsMatch) {
throw new e.ElectroError(
e.ErrorCodes.InconsistentIndexDefinition,
Expand All @@ -4427,6 +4431,20 @@ class Entity {
)}'. Key fields must have the same composite attribute definitions across all indexes they are involved with`,
);
}

const keyTemplatesMatch = pk.template === definition.template

if (!keyTemplatesMatch) {
throw new e.ElectroError(
e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate,
`Partition Key (pk) on Access Pattern '${u.formatIndexNameForDisplay(
accessPattern,
)}' is defined with the template ${pk.template || '(undefined)'}, but the accessPattern '${u.formatIndexNameForDisplay(
definition.index,
)}' defines this field with the key labels ${definition.template || '(undefined)'}'. Key fields must have the same template definitions across all indexes they are involved with`,
);
}

seenIndexFields[pk.field].push({ accessPattern, type: "pk" });
} else {
seenIndexFields[pk.field] = [];
Expand All @@ -4447,7 +4465,8 @@ class Entity {
const isAlsoDefinedAsPK = seenIndexFields[sk.field].find(
(field) => field.type === "pk",
);
if (isAlsoDefinedAsPK) {

if (isAlsoDefinedAsPK && !sk.isCustom) {
throw new e.ElectroError(
e.ErrorCodes.InconsistentIndexDefinition,
`The Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(
Expand All @@ -4456,16 +4475,19 @@ class Entity {
pk.field
}' which is already referenced by the Access Pattern(s) '${u.formatIndexNameForDisplay(
isAlsoDefinedAsPK.accessPattern,
)}' as a Partition Key. Fields mapped to Partition Keys cannot be also mapped to Sort Keys.`,
)}' as a Partition Key. Fields mapped to Partition Keys cannot be also mapped to Sort Keys unless their format is defined with a 'template'.`,
);
}

const definition = Object.values(facets.byField[sk.field]).find(
(definition) => definition.index !== indexName,
);

const definitionsMatch = validations.stringArrayMatch(
sk.facets,
definition.facets,
);
)

if (!definitionsMatch) {
throw new e.ElectroError(
e.ErrorCodes.DuplicateIndexFields,
Expand All @@ -4480,6 +4502,20 @@ class Entity {
)}'. Key fields must have the same composite attribute definitions across all indexes they are involved with`,
);
}

const keyTemplatesMatch = sk.template === definition.template

if (!keyTemplatesMatch) {
throw new e.ElectroError(
e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate,
`Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(
accessPattern,
)}' is defined with the template ${sk.template || '(undefined)'}, but the accessPattern '${u.formatIndexNameForDisplay(
definition.index,
)}' defines this field with the key labels ${definition.template || '(undefined)'}'. Key fields must have the same template definitions across all indexes they are involved with`,
);
}

seenIndexFields[sk.field].push({ accessPattern, type: "sk" });
} else {
seenIndexFields[sk.field] = [];
Expand Down
42 changes: 42 additions & 0 deletions test/definitions/reverseindex.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"KeySchema": [
{
"AttributeName": "pk",
"KeyType": "HASH"
},
{
"AttributeName": "sk",
"KeyType": "RANGE"
}
],
"AttributeDefinitions": [
{
"AttributeName": "pk",
"AttributeType": "S"
},
{
"AttributeName": "sk",
"AttributeType": "S"
}
],
"GlobalSecondaryIndexes": [
{
"IndexName": "reverse-index",
"KeySchema": [
{
"AttributeName": "sk",
"KeyType": "HASH"
},
{
"AttributeName": "pk",
"KeyType": "RANGE"
}
],
"Projection": {
"ProjectionType": "ALL"
}
}
],
"BillingMode": "PAY_PER_REQUEST"
}

61 changes: 61 additions & 0 deletions test/entity.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const troubleshoot = <Params extends any[], Response>(
fn: (...params: Params) => Response,
response: Response,
) => {};

const magnify = <T>(value: T): Resolve<T> => {
return {} as Resolve<T>;
};
Expand Down Expand Up @@ -254,3 +255,63 @@ type CustomAttributeEntityItemType = EntityItem<typeof customAttributeEntity>;
const unionEntityItem = {} as CustomAttributeEntityItemType["union"];

expectType<UnionType>(magnify(unionEntityItem));

const batchGetWithoutAttributesNoPreserve = entityWithSK.get([{attr1: 'abc', attr2: 'def'}]).go();
expectType<Promise<{
data: {
attr1: string;
attr2: string;
attr3?: "def" | "123" | "ghi" | undefined;
attr4: "abc" | "ghi";
attr5?: string | undefined;
attr6?: number | undefined;
attr7?: any;
attr8: boolean;
attr9?: number | undefined;
attr10?: boolean | undefined;
attr11?: string[] | undefined;
}[];
unprocessed: {
attr1: string;
attr2: string;
}[];
}>>(batchGetWithoutAttributesNoPreserve);

const batchGetWithoutAttributesPreserve = entityWithSK.get([{attr1: 'abc', attr2: 'def'}]).go({ preserveBatchOrder: true });
expectType<Promise<{
data: ({
attr1: string
attr2: string
attr3?: "123" | "def" | "ghi" | undefined
attr4: "abc" | "ghi"
attr5?: string | undefined
attr6?: number | undefined
attr7?: any
attr8: boolean
attr9?: number | undefined
attr10?: boolean | undefined
attr11?: string[] | undefined
} | null)[];
unprocessed: {
attr1: string;
attr2: string;
}[];
}>>(batchGetWithoutAttributesPreserve);

const batchGetWithAttributesNoPreserve = entityWithSK.get([{attr1: 'abc', attr2: 'def'}]).go({ attributes: ['attr5', 'attr10'] });
expectType<Promise<{
data: Array<{
attr5?: string | undefined;
attr10?: boolean | undefined;
}>;
unprocessed: { attr1: string; attr2: string; }[];
}>>(magnify(batchGetWithAttributesNoPreserve));

const batchGetWithAttributesPreserve = entityWithSK.get([{attr1: 'abc', attr2: 'def'}]).go({ attributes: ['attr5', 'attr10'], preserveBatchOrder: true });
expectType<Promise<{
data: Array<{
attr5?: string | undefined;
attr10?: boolean | undefined;
} | null>;
unprocessed: { attr1: string; attr2: string; }[];
}>>(magnify(batchGetWithAttributesPreserve));
3 changes: 2 additions & 1 deletion test/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const leadingUnderscoreKeys = require("./definitions/leadingunderscorekeys.json"
const localSecondaryIndexes = require("./definitions/localsecondaryindexes.json");
const keysOnly = require("./definitions/keysonly.json");
const castKeys = require("./definitions/castkeys.json");

const reverseIndex = require("./definitions/reverseindex.json");
const shouldDestroy = process.argv.includes("--recreate");

if (
Expand Down Expand Up @@ -79,6 +79,7 @@ async function main() {
createTable(dynamodb, "electro_localsecondaryindex", localSecondaryIndexes),
createTable(dynamodb, "electro_keysonly", keysOnly),
createTable(dynamodb, "electro_castkeys", castKeys),
createTable(dynamodb, "electro_reverseindex", reverseIndex),
]);
}

Expand Down
Loading
Loading