Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7b5b5fe
Multi-attribute index support
tywalch Dec 13, 2025
ab983f0
Adds tests and `between` query functionality
tywalch Dec 25, 2025
d8e072d
Adds tests for collections
tywalch Dec 26, 2025
772678f
Remove logs
tywalch Dec 26, 2025
3df4744
Fixes tests broken by error message refactor
tywalch Dec 27, 2025
a88a45b
Completes pagination tests
tywalch Dec 28, 2025
cbcac20
Composite index validation
tywalch Dec 29, 2025
94ef7d3
Adds secret->env refs
tywalch Dec 29, 2025
08475d2
Entity names/aliases and version values are the values used to unique…
tywalch Feb 7, 2026
a1098ef
Adds conversion tests
tywalch Feb 14, 2026
5e472ec
Adds partial sort key tests and documentation
tywalch Feb 15, 2026
a526cf1
Merge branch 'master' of https://github.com/tywalch/electrodb into fe…
tywalch Feb 21, 2026
e6d0a70
Merge branch 'master' of https://github.com/tywalch/electrodb into fe…
tywalch Feb 21, 2026
11a010f
Fixes missing import
tywalch Feb 21, 2026
79b7de9
Remove workflow envs
tywalch Feb 21, 2026
8e0861f
Cleans up single source of truth todo
tywalch Feb 22, 2026
b55f1c9
Cleans up multi-attribute index envs
tywalch Feb 22, 2026
49c32b3
Cleans up indexes.mdx
tywalch Feb 22, 2026
fa7b5ab
Cleans up indexes.mdx
tywalch Feb 22, 2026
70d3a3a
Cleans up indexes.mdx
tywalch Feb 22, 2026
9c82823
Cleans up indexes.mdx
tywalch Feb 22, 2026
79505e8
Cleans up indexes.mdx
tywalch Feb 22, 2026
7e8fcfd
Fixes tests
tywalch Feb 25, 2026
35ede27
Fixes error references
tywalch Feb 25, 2026
a92cce4
Clean up
tywalch Feb 26, 2026
fe4a99e
Adds 'scope' presence validation test, clean up
tywalch Feb 26, 2026
1ef1a90
Adds tests and polish
tywalch Feb 26, 2026
d3b1857
Apply suggestions from code review
tywalch Feb 26, 2026
871cf2d
Update www/src/pages/en/modeling/indexes.mdx
tywalch Feb 26, 2026
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
13 changes: 7 additions & 6 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type DocumentClient =
scan: DocumentClientMethod;
transactGet: DocumentClientMethod;
transactWrite: DocumentClientMethod;
query: DocumentClientMethod;
}
| {
send: (command: any) => Promise<any>;
Expand Down Expand Up @@ -3957,7 +3958,7 @@ export type NestedAttributes =

export interface IndexWithSortKey {
readonly sk: {
readonly field: string;
readonly field?: string;
readonly composite: ReadonlyArray<string>;
readonly template?: string;
};
Expand Down Expand Up @@ -3988,19 +3989,19 @@ export interface Schema<A extends string, F extends string, C extends string, P
readonly projection?: "all" | "keys_only" | ReadonlyArray<P>;
readonly index?: string;
readonly scope?: string;
readonly type?: "clustered" | "isolated";
readonly type?: "clustered" | "isolated" | "composite";
readonly collection?: AccessPatternCollection<C>;
readonly condition?: (composite: Record<string, unknown>) => boolean;
readonly pk: {
readonly casing?: KeyCasingOption;
readonly field: string;
readonly field?: string;
readonly composite: ReadonlyArray<F>;
readonly template?: string;
readonly cast?: KeyCastOption;
};
readonly sk?: {
readonly casing?: KeyCasingOption;
readonly field: string;
readonly field?: string;
readonly composite: ReadonlyArray<F>;
readonly template?: string;
readonly cast?: KeyCastOption;
Expand Down Expand Up @@ -4084,7 +4085,7 @@ type ClusteredIndexNameMap<
C extends string,
S extends Schema<A, F, C>,
> = {
[I in keyof S["indexes"] as S["indexes"][I] extends { type: "clustered" }
[I in keyof S["indexes"] as S["indexes"][I] extends { type: "clustered" | "composite" }
? I
: never]: S["indexes"][I]["collection"] extends AccessPatternCollection<
infer Name
Expand All @@ -4101,7 +4102,7 @@ type IsolatedIndexNameMap<
C extends string,
S extends Schema<A, F, C>,
> = {
[I in keyof S["indexes"] as S["indexes"][I] extends { type: "clustered" }
[I in keyof S["indexes"] as S["indexes"][I] extends { type: "clustered" | "composite" }
? never
: I]: S["indexes"][I]["collection"] extends AccessPatternCollection<
infer Name
Expand Down
Empty file modified localtestgrep.sh
100644 → 100755
Empty file.
149 changes: 90 additions & 59 deletions src/clauses.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,72 @@ const v = require("./validations");
const e = require("./errors");
const u = require("./util");

function handleNonIsolatedCollection(
entity,
state,
collection = "",
facets /* istanbul ignore next */ = {},
) {
if (state.getError() !== null) {
return state;
}
try {
const { pk, sk } = state.getCompositeAttributes();
return state
.setMethod(MethodTypes.query)
.setCollection(collection)
.setPK(entity._expectFacets(facets, pk))
.ifSK(() => {
const { composites, unused } = state.identifyCompositeAttributes(
facets,
sk,
pk,
);
state.setSK(composites);
state.beforeBuildParams(({ options, state }) => {
const accessPattern =
entity.model.translations.indexes.fromIndexToAccessPattern[
state.query.index
];

if (
options.compare === ComparisonTypes.attributes ||
options.compare === ComparisonTypes.v2
) {
if (
!entity.model.indexes[accessPattern].sk.isFieldRef &&
sk.length > 1
) {
state.filterProperties(FilterOperationNames.eq, {
...unused,
...composites,
});
}
}
});
})
.whenOptions(({ options, state }) => {
if (!options.ignoreOwnership && !state.getParams()) {
state.query.options.expressions.names = {
...state.query.options.expressions.names,
...state.query.options.identifiers.names,
};
state.query.options.expressions.values = {
...state.query.options.expressions.values,
...state.query.options.identifiers.values,
};
state.query.options.expressions.expression =
state.query.options.expressions.expression.length > 1
? `(${state.query.options.expressions.expression}) AND ${state.query.options.identifiers.expression}`
: `${state.query.options.identifiers.expression}`;
}
});
} catch (err) {
state.setError(err);
return state;
}
}

const methodChildren = {
upsert: [
"upsertSet",
Expand Down Expand Up @@ -86,6 +152,7 @@ let clauses = {
"scan",
"collection",
"clusteredCollection",
"compositeCollection",
"create",
"remove",
"patch",
Expand All @@ -94,6 +161,23 @@ let clauses = {
"batchGet",
],
},
compositeCollection: {
name: "compositeCollection",
action(
entity,
state,
collection = "",
facets /* istanbul ignore next */ = {},
) {
return handleNonIsolatedCollection(
entity,
state.setType(QueryTypes.composite_collection),
collection,
facets,
);
},
children: ["between", "gte", "gt", "lte", "lt", "begins", "params", "go"],
},
clusteredCollection: {
name: "clusteredCollection",
action(
Expand All @@ -102,65 +186,12 @@ let clauses = {
collection = "",
facets /* istanbul ignore next */ = {},
) {
if (state.getError() !== null) {
return state;
}
try {
const { pk, sk } = state.getCompositeAttributes();
return state
.setType(QueryTypes.clustered_collection)
.setMethod(MethodTypes.query)
.setCollection(collection)
.setPK(entity._expectFacets(facets, pk))
.ifSK(() => {
const { composites, unused } = state.identifyCompositeAttributes(
facets,
sk,
pk,
);
state.setSK(composites);
state.beforeBuildParams(({ options, state }) => {
const accessPattern =
entity.model.translations.indexes.fromIndexToAccessPattern[
state.query.index
];

if (
options.compare === ComparisonTypes.attributes ||
options.compare === ComparisonTypes.v2
) {
if (
!entity.model.indexes[accessPattern].sk.isFieldRef &&
sk.length > 1
) {
state.filterProperties(FilterOperationNames.eq, {
...unused,
...composites,
});
}
}
});
})
.whenOptions(({ options, state }) => {
if (!options.ignoreOwnership && !state.getParams()) {
state.query.options.expressions.names = {
...state.query.options.expressions.names,
...state.query.options.identifiers.names,
};
state.query.options.expressions.values = {
...state.query.options.expressions.values,
...state.query.options.identifiers.values,
};
state.query.options.expressions.expression =
state.query.options.expressions.expression.length > 1
? `(${state.query.options.expressions.expression}) AND ${state.query.options.identifiers.expression}`
: `${state.query.options.identifiers.expression}`;
}
});
} catch (err) {
state.setError(err);
return state;
}
return handleNonIsolatedCollection(
entity,
state.setType(QueryTypes.clustered_collection),
collection,
facets,
);
},
children: ["between", "gte", "gt", "lte", "lt", "begins", "params", "go"],
},
Expand Down
Loading
Loading