Skip to content

Validations

effect-dynamodb validates entity definitions, index configurations, and query inputs at multiple levels. Each validation has a structured error code (EDD-XXXX) for programmatic handling.

CodeRuleLevelDescription
EDD-9001Empty primary keyCompile + RuntimePrimary key must have at least one composite attribute in pk or sk
EDD-9002Unknown composite attributeCompile + RuntimeComposite attribute references a field not in the model
EDD-9003Invalid GSI formatCompile + RuntimeGSI index must be { name, pk, sk } object, not a string
EDD-9004SK prefix violationCompile + RuntimeSort key composites must follow prefix ordering
EDD-9005SK composite in filterCompileSort key composite used in .filter() instead of query input
EDD-9006Unknown query propertyCompile + RuntimeQuery input contains a property that is not a composite attribute
EDD-9007Field rename collisionCompile + RuntimeDynamoModel.configure renames a field to a name that already exists on the model

Compile = Detected by the Language Service Plugin (IDE squiggles before running code) Runtime = Detected when the code executes (throws Error or ValidationError)


The primary key must have at least one composite attribute across pk and sk.

// ❌ Error: primary key must have at least one composite attribute
const BadEntity = Entity.make({
model: User,
entityType: "User",
primaryKey: {
pk: { field: "pk", composite: [] },
sk: { field: "sk", composite: [] },
},
})

Fix: Add at least one composite attribute to pk or sk:

// ✅ Correct
primaryKey: {
pk: { field: "pk", composite: ["userId"] },
sk: { field: "sk", composite: [] },
}

All composite attributes must exist as fields on the model.

class User extends Schema.Class<User>("User")({
userId: Schema.String,
name: Schema.String,
}) {}
// ❌ Error: references unknown attribute "email"
const Users = Entity.make({
model: User,
entityType: "User",
primaryKey: {
pk: { field: "pk", composite: ["userId"] },
sk: { field: "sk", composite: [] },
},
indexes: {
byEmail: {
name: "gsi1",
pk: { field: "gsi1pk", composite: ["email"] }, // Not in User model
sk: { field: "gsi1sk", composite: [] },
},
},
})

Fix: Use field names that exist on the model, or add the field to the model.

GSI indexes must use the new format with name, pk: { field, composite }, and sk: { field, composite }.

// ❌ Error: old format with nested index object
indexes: {
byEmail: {
index: { name: "gsi1", pk: "gsi1pk", sk: "gsi1sk" }, // ❌ legacy nested-`index` shape
composite: ["email"],
sk: [],
},
}
// ✅ Correct (current shape — `name`, `pk`, `sk` siblings)
indexes: {
byEmail: {
name: "gsi1",
pk: { field: "gsi1pk", composite: ["email"] },
sk: { field: "gsi1sk", composite: [] },
},
}

Sort key composites must be provided in order. If your SK is ["status", "title"], you can provide:

  • {} (no SK composites)
  • { status } (first composite only)
  • { status, title } (both composites)

But not { title } (skipping status).

const Tasks = Entity.make({
// ...
indexes: {
byProject: {
name: "gsi1",
pk: { field: "gsi1pk", composite: ["projectId"] },
sk: { field: "gsi1sk", composite: ["status", "title"] },
},
},
})
// ❌ Error: "title" requires prior composite "status"
db.entities.Tasks.byProject({ projectId: "p-1", title: "hello" })
// ✅ Correct — provide composites in order
db.entities.Tasks.byProject({ projectId: "p-1", status: "active", title: "hello" })
db.entities.Tasks.byProject({ projectId: "p-1", status: "active" })
db.entities.Tasks.byProject({ projectId: "p-1" })

When a sort key composite is used in .filter() instead of the query input, DynamoDB reads all items matching the PK and then filters client-side. Moving the composite to the query input uses KeyConditionExpression for server-side filtering, which is more efficient.

// ⚠️ Warning: "status" is an SK composite — use in query input
db.entities.Tasks
.byProject({ projectId: "p-1" })
.filter({ status: "active" }) // Post-read filter (reads ALL items first)
.collect()
// ✅ Efficient — SK composite in query input
db.entities.Tasks
.byProject({ projectId: "p-1", status: "active" }) // Key condition (server-side)
.collect()

Query inputs can only contain composite attributes defined on the index.

// ❌ Error: "bogus" is not a composite attribute for "byProject"
db.entities.Tasks.byProject({ projectId: "p-1", bogus: "x" })
// Valid attributes for byProject: projectId (PK), status (SK)

DynamoModel.configure renames a field to a DynamoDB attribute name that already exists as another field on the model. This causes both model fields to map to the same physical DynamoDB attribute.

class User extends Schema.Class<User>("User")({
id: Schema.String,
name: Schema.String,
}) {}
// ❌ Error: renaming `id` to `name` collides with existing model field `name`
const UserModel = DynamoModel.configure(User, { id: { field: "name" } })

Fix: Choose a unique DynamoDB attribute name:

// ✅ Correct — "userId" doesn't collide with any model field
const UserModel = DynamoModel.configure(User, { id: { field: "userId" } })

The Language Service Plugin reports EDD diagnostics as IDE errors and warnings. Install it to get real-time validation as you type:

Terminal window
pnpm add -D @effect-dynamodb/language-service
{
"compilerOptions": {
"plugins": [{ "name": "@effect-dynamodb/language-service" }]
}
}

Diagnostics appear as:

  • Errors (red squiggles): EDD-9001, EDD-9002, EDD-9003, EDD-9004, EDD-9006, EDD-9007
  • Warnings (yellow squiggles): EDD-9005