Skip to content

ElectroDB Comparison

ElectroDB is the most feature-rich existing DynamoDB ORM for TypeScript. This comparison maps its capabilities against effect-dynamodb to help teams evaluate which library best fits their needs.

Methodology: Complete feature inventory of ElectroDB (from electrodb.dev documentation) compared against effect-dynamodb capabilities.


FeatureElectroDBeffect-dynamodbAssessment
Schema definitionInline JS objects with type inferenceEffect Schema (Schema.Class/Struct)Advantage — richer validation, bidirectional transforms, branded types
Attribute typesstring, number, boolean, map, list, set, any, CustomAttributeTypeAll Effect Schema types including DateTime, branded, unionsAdvantage — full Schema ecosystem
Required attributesrequired: true propertySchema-level required/optionalParity
Default valuesdefault: value | () => valueSchema defaults (Schema.withDefault)Parity
Attribute validationvalidate: RegExp | callbackSchema .check() with named factoriesAdvantage — composable, named checks
Read-only after creationreadOnly: trueDynamoModel.configure(model, { field: { immutable: true } })Parity
Field aliasingfield: "dbFieldName"DynamoModel.configure(model, { field: { field: "dbName" } })Parity
Hidden attributeshidden: true (excluded from responses)DynamoModel.Hidden annotationParity
Enum attributesArray of allowed valuesSchema.Literals([...])Parity
Opaque/branded typesCustomAttributeType<T>, OpaquePrimitiveTypeEffect Schema branded typesAdvantage — first-class support with compile-time enforcement
Date/time handlingManual (string/number attributes)9 built-in date schemas (DateString, DateEpochMs, DateEpochSeconds, DateTimeZoned, etc.) with storedAs modifierAdvantage — type-safe date handling with configurable storage
Attribute labels for keyslabel: "customPrefix"Not supported (attribute name used)ElectroDB only
Attribute padding in keyspadding: { length, char }Automatic zero-padding for numeric composites (16 digits for numbers, 38 for bigints)Parity
Getter/setter hooksget/set callbacks per attributeUse Schema.transform / Schema.transformOrFailDifferent approach — see note 1
Watch attribute changeswatch: ["attr1"] | "*"Not applicableDifferent approach — see note 1
Calculated attributesVia watch + setNot applicableDifferent approach — see note 1
Virtual attributesVia watch + get (never persisted)Not applicableDifferent approach — see note 1
DynamoDB native Set typetype: "set", items: “string”/“number”Schema.ReadonlySet(Schema.String) / Schema.ReadonlySet(Schema.Number) — marshalls to native SS/NS; Entity.add() (atomic addition), Entity.deleteFromSet() (atomic removal)Parity

Note 1: ElectroDB’s getter/setter/watch/calculated/virtual attributes follow an imperative callback paradigm. Effect Schema provides equivalent power through composable, declarative transformations (Schema.transform, Schema.transformOrFail, custom annotations). These represent a different design philosophy rather than a missing capability.

FeatureElectroDBeffect-dynamodbAssessment
Named access patternsYesYes (index names on Entity)Parity
Composite key compositionAttribute arraysElectroDB-style attribute arraysParity
Key casing"upper"/"lower"/"none"/"default""lowercase"/"uppercase"/"preserve"Parity
Isolated indexestype: "isolated" (default)type: "isolated" (default)Parity
Clustered indexestype: "clustered"Supported (opt-in via type: "clustered"; required for sub-collections)Parity
Sparse indexescondition callbackAuto-sparse via tryComposeIndexKeys (missing composites skip GSI)Parity
Sub-collectionscollection: ["parent", "child"]collection: ["parent", "child"]Parity
Custom key templatestemplate: "${attr}_suffix"Not supported (attribute-list only)ElectroDB only — intentional (see note 2)
Composite index typetype: "composite" (separate columns per attribute)Not supportedElectroDB only
Index scopescope string for entity isolationNot supportedElectroDB only
Key castingcast: "number" for numeric sort keysNot supported (string keys only)ElectroDB only
Attributes as indexesWhen attribute field matches index fieldNot supportedElectroDB only

Note 2: Key templates ("USER#${userId}") offer maximum flexibility but introduce string-based complexity. Attribute-list composition is simpler, more predictable, and covers standard single-table patterns. This is an intentional design choice.

FeatureElectroDBeffect-dynamodbAssessment
Get (single item)entity.get(pk).go()bound.get(key)Parity
Query by access patternentity.query.<name>(pk).go()db.entities.E.indexName(pk).collect()Parity
Sort key conditionsbegins, between, gt, gte, lt, lteeq, lt, lte, gt, gte, between, beginsWithParity
Filter expressions.where((attrs, ops) => ...) callback`Entity.filter(callbackshorthand)` declarative object
Scanentity.scan.go()db.entities.E.scan().collect()Parity
Collection queryservice.collections.<name>(pk)db.collections.name(pk).collect()Parity
Batch getentity.get([...]).go()Batch.get(items) with auto-retryParity
Transact getservice.transaction.get(cb)Transaction.transactGet(items)Parity
Consistent readsconsistent: trueEntity.consistentRead(), Query.consistentRead()Parity
Projection expressionattributes: [...]`Entity.select(callbackattrs)`
Page count limitingpages: NQuery.maxPages(n)Parity
Count modeCount targetQuery.count terminal (uses Select: "COUNT")Parity
Params-only mode.params() returns request without executingQuery.asParamsParity
Ignore entity ownershipignoreOwnership: trueQuery.ignoreOwnershipParity
Auto-paginationpages: "all"bound.collect(query) fetches all pagesParity
Cursor-based paginationcursor in responsebound.paginate(query) returns Stream, Query.execute(query) returns single page with cursorAdvantage — Stream-based pagination is more composable
Response data format"attributes"/"includeKeys"/"raw"4 decode modes: asModel/asRecord/asItem/asNativeAdvantage — richer decode options
Preserve batch orderpreserveBatchOrder: truePositional tuple matching (always ordered)Advantage — ordered by default
Find (auto index selection)entity.find(attrs).go()Not supported — use explicit entity.query.<indexName>()ElectroDB only
Match (auto index + filter)entity.match(attrs).go()Not supported — use explicit query + filterElectroDB only
Hydrate (KEYS_ONLY GSI)hydrate: true auto batch-getNot supported — compose manually: query GSI then Batch.getElectroDB only
FeatureElectroDBeffect-dynamodbAssessment
Put (create/overwrite)entity.put(data).go()bound.put(input)Parity
Create (fail if exists)entity.create(data).go()bound.create(input)Parity
Upsert (create or update)entity.upsert(data).go()bound.upsert(input)Parity
Updateentity.update(pk).set({}).go()bound.update(key).set(updates)Parity
Patch (fail if not exists)entity.patch(pk).set({}).go()bound.patch(key).set(updates)Parity
Deleteentity.delete(pk).go()bound.delete(key)Parity
Remove (fail if not exists)entity.remove(pk).go()bound.deleteIfExists(key)Parity
Conditional writes.where() on any mutation.condition(callback | shorthand) on put/update/delete buildersParity
Batch put/deleteentity.put([...]).go(), entity.delete([...]).go()Batch.write(ops) with auto-retryParity
Transact writeservice.transaction.write(cb)Transaction.transactWrite(ops)Parity
Condition check (transaction).check().where().commit()Transaction.check(condition)Parity
Return values controlresponse: "all_old"/"all_new"/etc..returnValues(mode) on BoundUpdate/BoundDeleteParity
FeatureElectroDBeffect-dynamodbAssessment
SET (replace values).set({}).set(updates) on BoundUpdateParity
REMOVE (delete attributes).remove([]).remove(fields) on BoundUpdateParity
ADD (increment / add to set).add({}).add(values) on BoundUpdateParity
SUBTRACT (decrement).subtract({}).subtract(values) on BoundUpdateParity
APPEND (add to list).append({}).append(values) on BoundUpdateParity
DELETE (remove from set).delete({}).deleteFromSet(values) on BoundUpdateParity
Data callback.data((attrs, ops) => ...)Fluent methods on BoundUpdate: .set(), .add(), .pathSet(), etc.Parity
Nested property updatesDot notation for maps, brackets for listsPath-based methods on BoundUpdate: .pathSet(), .pathRemove(), .pathAdd(), etc.Parity
FeatureElectroDBeffect-dynamodbAssessment
Entity isolation__edb_e__ + __edb_v____edd_e__ discriminatorParity
Schema versioningmodel.versionDynamoSchema.versionParity
Application namespacemodel.serviceDynamoSchema.nameParity
DynamoDB Streams parsingentity.parse() utilityEntity.itemSchema() and Entity.decodeMarshalledItem()Parity
TimestampsManual (watch/set/default recipe)Built-in timestamps config with configurable storage formatAdvantage
Version trackingNot built-inBuilt-in versioned config with auto-incrementAdvantage
Version history/snapshotsNot built-inBuilt-in versioned: { retain: true } — atomic snapshot on every writeAdvantage
Version retrievalNot built-inEntity.getVersion(key, version), Entity.versions(key)Advantage
Soft deleteNot built-inBuilt-in softDelete config with optional TTLAdvantage
Restore soft-deletedNot built-inEntity.restore(key) — recomposes all keys and unique sentinelsAdvantage
Purge (full partition)Not built-inEntity.purge(key) — deletes item + versions + sentinelsAdvantage
Soft-deleted item accessNot built-inEntity.deleted.get(key), Entity.deleted.list(key)Advantage
Unique constraintsManual via transactionsBuilt-in unique config — sentinel-based with atomic transactionsAdvantage
Optimistic lockingNot built-inBuilt-in .expectedVersion(n) on BoundUpdateAdvantage
IdempotencyManual via transaction tokensBuilt-in via unique constraintsAdvantage
Conversion utilitiesComposites ↔ Keys ↔ CursorsUse KeyComposer functions directlyDifferent approach
Event listeners/logginglisteners/logger callbacksUse Effect tracing/logging (Effect.tap, Effect.log, spans)Different approach
Custom params mergeparams option merged into requestNot supportedElectroDB only
FeatureElectroDBeffect-dynamodbAssessment
Graph-based aggregatesNot built-inAggregate.make() — multi-entity composite domain modelsAdvantage
Edge types (one/many/ref)Not built-inAggregate.one(), Aggregate.many(), Aggregate.ref()Advantage
Sub-aggregatesNot built-inRecursive sub-aggregate composition via .with()Advantage
Aggregate create (atomic)Not built-inAtomic transactional decomposition + writeAdvantage
Aggregate update (optic-based)Not built-inAggregate.update() with cursor/optic mutation contextAdvantage
Ref hydration on readNot built-inAutomatic entity hydration for one/many/ref edgesAdvantage
Discriminator-based edgesNot built-inOneEdge and BoundSubAggregate with discriminator SK formatAdvantage
FeatureElectroDBeffect-dynamodbAssessment
Event streamsNot built-inEventStore.makeStream() — append-only event log per streamAdvantage
Command handlersNot built-inEventStore.commandHandler() — decider pattern with state foldAdvantage
Stream fold/projectionNot built-inEventStore.fold(), EventStore.foldFrom()Advantage
FeatureElectroDBeffect-dynamodbAssessment
Type inference from schemaInline schemas get inference; external need as constFull inference from Effect SchemaAdvantage
Item typeEntityItem<E>Entity.Record<E>Parity
Identity/key typeEntityIdentifiers<E>Entity.Key<E>Parity
Create input typeCreateEntityItem<E>Entity.Input<E>Parity
Update input typeUpdateEntityItem<E>Entity.Update<E>Parity
Marshalled typeNot built-inEntity.Marshalled<E>Advantage
Item with keys typeNot separateEntity.Item<E>Advantage
7 derived typesN/AModel, Record, Input, Update, Key, Item, MarshalledAdvantage
Ref-aware input typesN/AEntity fields auto-transform to fieldId: string in InputAdvantage
FeatureElectroDBeffect-dynamodbAssessment
AWS SDK v3 supportYesYes (via DynamoClient)Parity
Layer-based DIN/A (plain JS)Full Effect Layer/Service integrationAdvantage
Resource managementManual client lifecycleEffect.acquireRelease in DynamoClientAdvantage
Error handlingElectroError with codes20 tagged errors with catchTag discriminationAdvantage
Streaming paginationNot built-in (cursor loops)Stream.paginate integrationAdvantage
Table definition exportNot built-inTable.definition() for CloudFormation/CDKAdvantage
Config-based setupN/ADynamoClient.layerConfig(), Table.layerConfig() via Effect ConfigAdvantage
Dual APIs (pipe support)N/A (fluent chaining only)Function.dual on all public functionsAdvantage
Geospatial queriesNot built-in@effect-dynamodb/geo — H3-based proximity searchAdvantage
Language service pluginNot built-in@effect-dynamodb/language-service — hover tooltips showing DynamoDB operationsAdvantage
Interactive playgroundelectrodb.funBuilt-in docs playgroundParity
CLI toolingElectroCLINot built-inElectroDB only
AWS SDK v2 supportYesNoN/A (v2 is deprecated)

CategoryParityeffect-dynamodb AdvantageElectroDB Only
Data Modeling933 (3 philosophical)
Indexes606 (1 intentional)
Read Operations1433
Write Operations1200
Update Expressions602
Lifecycle & Data Integrity3111
Aggregates & Domain Modeling070
Event Sourcing030
Type System450
Integration & DX2101
Total564216

These are capabilities that ElectroDB does not provide:

  1. Built-in lifecycle management — Timestamps, versioning with snapshot history, soft delete with restore/purge — all declarative config. ElectroDB requires manual implementation for each.

  2. Built-in data integrity — Unique constraints via sentinel items, optimistic locking via expectedVersion(), and idempotency — all enforced atomically. ElectroDB has no built-in concurrency control.

  3. Graph-based aggregatesAggregate.make() binds multi-entity hierarchies (one/many/ref edges) to a single partition, with atomic create, optic-based update, and automatic hydration on read. No equivalent in ElectroDB.

  4. Event sourcingEventStore.makeStream() provides append-only event logs with command handlers and state fold projections built on DynamoDB.

  5. Effect integration — Full Effect ecosystem: Layer-based DI, 20 tagged errors with catchTag, Stream-based pagination, resource management, composable pipelines, dual APIs. This is the primary architectural differentiator.

  6. 7 derived types — One entity declaration automatically produces Model, Record, Input, Update, Key, Item, and Marshalled types (ref-aware Input/Update variants are derived from these). ElectroDB has fewer derived types and no marshalled/item distinction.

  7. Date schema system — 9 built-in date schemas covering DateTime.Utc, DateTime.Zoned, and native Date with configurable storage formats (ISO string, epoch ms, epoch seconds) and a storedAs modifier for runtime format switching.

  8. 4 decode modesasModel (clean domain), asRecord (with system fields), asItem (with DynamoDB keys), asNative (raw AttributeValue). ElectroDB has 3 data modes.

  9. Table definition exportTable.definition() generates CreateTableCommandInput from entity declarations for CloudFormation/CDK/testing.

  10. Ecosystem packages@effect-dynamodb/geo for H3-based geospatial proximity queries, @effect-dynamodb/language-service for IDE hover tooltips showing DynamoDB operations.


Features in ElectroDB that are not available in effect-dynamodb, with workarounds where applicable:

FeatureWorkaround
Find/Match (auto index selection)Use explicit entity.query.<indexName>() — more predictable than auto-selection
Custom key templatesAttribute-list composition covers standard patterns
Getter/setter hooksSchema.transform / Schema.transformOrFail provide equivalent power
Watch/calculated/virtual attributesCompute at application layer or use Schema.transform
Attribute paddingAutomatic zero-padding for numeric composites in sort keys
Composite index typeDynamoDB feature from Nov 2025 — can be added in a future release
Key casting (numeric keys)All keys are strings by design
Index scopeKey namespacing provides sufficient isolation
Attributes as indexesUse standard composite key definitions
Data callbackFluent methods on BoundUpdate: .set(), .add(), .pathSet(), etc.
Nested property updatesPath-based methods on BoundUpdate: .pathSet(), .pathRemove(), .pathAdd(), etc.
Custom params mergeUse DynamoClient directly for custom operations
ElectroCLIN/A — no CLI equivalent
Hydrate (KEYS_ONLY GSI)Query GSI then compose Batch.get(keys) in a pipe

See the Migration from ElectroDB guide for side-by-side code examples.


If you…Choose
Already use Effect TSeffect-dynamodb — native integration, no impedance mismatch
Need lifecycle management (versioning, soft delete, restore)effect-dynamodb — built-in, declarative config
Need aggregate/composite domain modelseffect-dynamodb — graph-based aggregates with atomic operations
Need event sourcing on DynamoDBeffect-dynamodb — built-in EventStore module
Need geospatial querieseffect-dynamodb — H3-based proximity search via @effect-dynamodb/geo
Want minimal setup for basic CRUDElectroDB — less boilerplate, fluent chaining
Need custom key templatesElectroDB — template string support
Need auto index selection (find/match)ElectroDB — automatic index matching
Prefer imperative hooks (get/set/watch)ElectroDB — callback-based attribute transforms