Skip to content
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
2 changes: 0 additions & 2 deletions .eslintignore

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [ 16.x, 18.x, 20.x ]
node-version: [ 20.x, 22.x, 24.x ]
os: [ windows-latest, ubuntu-latest, macOS-latest ]

# Go
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ coverage*
v8.log
package-lock.json
.nyc_output
.kiro
6 changes: 3 additions & 3 deletions actions/batchGetItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ module.exports = function batchGetItem (store, data, cb) {
for (table in tableResponses) {
// Order is pretty random
// Assign keys before we shuffle
tableResponses[table].forEach(function (tableRes, ix) { tableRes._key = data.RequestItems[table].Keys[ix] }) // eslint-disable-line no-loop-func
tableResponses[table].forEach(function (tableRes, ix) { tableRes._key = data.RequestItems[table].Keys[ix] })
shuffle(tableResponses[table])
res.Responses[table] = tableResponses[table].map(function (tableRes) { // eslint-disable-line no-loop-func
res.Responses[table] = tableResponses[table].map(function (tableRes) {
if (tableRes.Item) {
// TODO: This is totally inefficient - should fix this
var newSize = totalSize + db.itemSize(tableRes.Item)
if (newSize > (1024 * 1024 + store.options.maxItemSize - 3)) {
if (newSize > ((1024 * 1024) + store.options.maxItemSize - 3)) {
if (!res.UnprocessedKeys[table]) {
res.UnprocessedKeys[table] = { Keys: [] }
if (data.RequestItems[table].AttributesToGet)
Expand Down
126 changes: 71 additions & 55 deletions actions/createTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ module.exports = function createTable (store, data, cb) {
tableDb.lock(key, function (release) {
cb = release(cb)

tableDb.get(key, function (err) {
tableDb.get(key, function (err, existingTable) {
if (err && err.name != 'NotFoundError') return cb(err)
if (!err) {

// Check if table exists and is valid
if (!err && existingTable && typeof existingTable === 'object' && existingTable.TableStatus) {
err = new Error
err.statusCode = 400
err.body = {
Expand All @@ -19,69 +21,83 @@ module.exports = function createTable (store, data, cb) {
return cb(err)
}

data.TableArn = 'arn:aws:dynamodb:' + tableDb.awsRegion + ':' + tableDb.awsAccountId + ':table/' + data.TableName
data.TableId = uuidV4()
data.CreationDateTime = Date.now() / 1000
data.ItemCount = 0
if (!data.ProvisionedThroughput) {
data.ProvisionedThroughput = { ReadCapacityUnits: 0, WriteCapacityUnits: 0 }
}
data.ProvisionedThroughput.NumberOfDecreasesToday = 0
data.TableSizeBytes = 0
data.TableStatus = 'CREATING'
if (data.BillingMode == 'PAY_PER_REQUEST') {
data.BillingModeSummary = { BillingMode: 'PAY_PER_REQUEST' }
data.TableThroughputModeSummary = { TableThroughputMode: 'PAY_PER_REQUEST' }
delete data.BillingMode
}
if (data.LocalSecondaryIndexes) {
data.LocalSecondaryIndexes.forEach(function (index) {
index.IndexArn = 'arn:aws:dynamodb:' + tableDb.awsRegion + ':' + tableDb.awsAccountId + ':table/' +
data.TableName + '/index/' + index.IndexName
index.IndexSizeBytes = 0
index.ItemCount = 0
// If table exists but is corrupted, delete it first
if (!err && existingTable && (!existingTable.TableStatus || typeof existingTable !== 'object')) {
tableDb.del(key, function () {
// Ignore deletion errors and proceed with creation
createNewTable()
})
return
}
if (data.GlobalSecondaryIndexes) {
data.GlobalSecondaryIndexes.forEach(function (index) {
index.IndexArn = 'arn:aws:dynamodb:' + tableDb.awsRegion + ':' + tableDb.awsAccountId + ':table/' +

// Table doesn't exist, create it
createNewTable()

function createNewTable () {
data.TableArn = 'arn:aws:dynamodb:' + tableDb.awsRegion + ':' + tableDb.awsAccountId + ':table/' + data.TableName
data.TableId = uuidV4()
data.CreationDateTime = Date.now() / 1000
data.ItemCount = 0
if (!data.ProvisionedThroughput) {
data.ProvisionedThroughput = { ReadCapacityUnits: 0, WriteCapacityUnits: 0 }
}
data.ProvisionedThroughput.NumberOfDecreasesToday = 0
data.TableSizeBytes = 0
data.TableStatus = 'CREATING'
if (data.BillingMode == 'PAY_PER_REQUEST') {
data.BillingModeSummary = { BillingMode: 'PAY_PER_REQUEST' }
data.TableThroughputModeSummary = { TableThroughputMode: 'PAY_PER_REQUEST' }
delete data.BillingMode
}
if (data.LocalSecondaryIndexes) {
data.LocalSecondaryIndexes.forEach(function (index) {
index.IndexArn = 'arn:aws:dynamodb:' + tableDb.awsRegion + ':' + tableDb.awsAccountId + ':table/' +
data.TableName + '/index/' + index.IndexName
index.IndexSizeBytes = 0
index.ItemCount = 0
index.IndexStatus = 'CREATING'
if (!index.ProvisionedThroughput) {
index.ProvisionedThroughput = { ReadCapacityUnits: 0, WriteCapacityUnits: 0 }
}
index.ProvisionedThroughput.NumberOfDecreasesToday = 0
})
}
index.IndexSizeBytes = 0
index.ItemCount = 0
})
}
if (data.GlobalSecondaryIndexes) {
data.GlobalSecondaryIndexes.forEach(function (index) {
index.IndexArn = 'arn:aws:dynamodb:' + tableDb.awsRegion + ':' + tableDb.awsAccountId + ':table/' +
data.TableName + '/index/' + index.IndexName
index.IndexSizeBytes = 0
index.ItemCount = 0
index.IndexStatus = 'CREATING'
if (!index.ProvisionedThroughput) {
index.ProvisionedThroughput = { ReadCapacityUnits: 0, WriteCapacityUnits: 0 }
}
index.ProvisionedThroughput.NumberOfDecreasesToday = 0
})
}

tableDb.put(key, data, function (err) {
if (err) return cb(err)
tableDb.put(key, data, function (err) {
if (err) return cb(err)

setTimeout(function () {
setTimeout(function () {

// Shouldn't need to lock/fetch as nothing should have changed
data.TableStatus = 'ACTIVE'
if (data.GlobalSecondaryIndexes) {
data.GlobalSecondaryIndexes.forEach(function (index) {
index.IndexStatus = 'ACTIVE'
})
}
// Shouldn't need to lock/fetch as nothing should have changed
data.TableStatus = 'ACTIVE'
if (data.GlobalSecondaryIndexes) {
data.GlobalSecondaryIndexes.forEach(function (index) {
index.IndexStatus = 'ACTIVE'
})
}

if (data.BillingModeSummary) {
data.BillingModeSummary.LastUpdateToPayPerRequestDateTime = data.CreationDateTime
}
if (data.BillingModeSummary) {
data.BillingModeSummary.LastUpdateToPayPerRequestDateTime = data.CreationDateTime
}

tableDb.put(key, data, function (err) {
// eslint-disable-next-line no-console
if (err && !/Database is not open/.test(err)) console.error(err.stack || err)
})
tableDb.put(key, data, function (err) {

if (err && !/Database is (not open|closed)/.test(err)) console.error(err.stack || err)
})

}, store.options.createTableMs)
}, store.options.createTableMs)

cb(null, { TableDescription: data })
})
cb(null, { TableDescription: data })
})
}
})
})

Expand Down
16 changes: 14 additions & 2 deletions actions/deleteTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ module.exports = function deleteTable (store, data, cb) {
store.getTable(key, false, function (err, table) {
if (err) return cb(err)

// Handle corrupted table entries
if (!table || typeof table !== 'object') {
// Table entry is corrupted, treat as if table doesn't exist
err = new Error
err.statusCode = 400
err.body = {
__type: 'com.amazonaws.dynamodb.v20120810#ResourceNotFoundException',
message: 'Requested resource not found: Table: ' + key + ' not found',
}
return cb(err)
}

// Check if table is ACTIVE or not?
if (table.TableStatus == 'CREATING') {
err = new Error
Expand Down Expand Up @@ -38,8 +50,8 @@ module.exports = function deleteTable (store, data, cb) {

setTimeout(function () {
tableDb.del(key, function (err) {
// eslint-disable-next-line no-console
if (err && !/Database is not open/.test(err)) console.error(err.stack || err)

if (err && !/Database is (not open|closed)/.test(err)) console.error(err.stack || err)
})
}, store.options.deleteTableMs)

Expand Down
19 changes: 14 additions & 5 deletions actions/listTables.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@ var once = require('once'),

module.exports = function listTables (store, data, cb) {
cb = once(cb)
var opts, limit = data.Limit || 100
var opts = {}, limit = data.Limit || 100

if (data.ExclusiveStartTableName)
opts = { gt: data.ExclusiveStartTableName }
// Don't use opts.gt since it doesn't work in this LevelDB implementation
// We'll filter manually after getting all results

db.lazy(store.tableDb.createKeyStream(opts), cb)
.take(limit + 1)
.take(Infinity) // Take all items since we need to filter manually
.join(function (names) {
// Filter to implement proper ExclusiveStartTableName behavior
// LevelDB's gt option doesn't work properly in this implementation
if (data.ExclusiveStartTableName) {
names = names.filter(function (name) {
return name > data.ExclusiveStartTableName
})
}

// Apply limit after filtering
var result = {}
if (names.length > limit) {
names.splice(limit)
names = names.slice(0, limit)
result.LastEvaluatedTableName = names[names.length - 1]
}
result.TableNames = names
Expand Down
19 changes: 17 additions & 2 deletions actions/listTagsOfResource.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,23 @@ module.exports = function listTagsOfResource (store, data, cb) {
}
if (err) return cb(err)

db.lazy(store.getTagDb(tableName).createReadStream(), cb).join(function (tags) {
cb(null, { Tags: tags.map(function (tag) { return { Key: tag.key, Value: tag.value } }) })
// Get both keys and values from the tag database
var tagDb = store.getTagDb(tableName)
var keys = []
var values = []

db.lazy(tagDb.createKeyStream(), cb).join(function (tagKeys) {
keys = tagKeys
db.lazy(tagDb.createValueStream(), cb).join(function (tagValues) {
values = tagValues

// Combine keys and values into tag objects
var tags = keys.map(function (key, index) {
return { Key: key, Value: values[index] }
})

cb(null, { Tags: tags })
})
})
})
}
Expand Down
8 changes: 4 additions & 4 deletions actions/updateItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function applyAttributeUpdates (updates, table, item) {
return db.validationError('Type mismatch for attribute to update')
if (!item[attr]) item[attr] = {}
if (!item[attr][type]) item[attr][type] = []
var val = type == 'L' ? update.Value[type] : update.Value[type].filter(function (a) { // eslint-disable-line no-loop-func
var val = type == 'L' ? update.Value[type] : update.Value[type].filter(function (a) {
return !~item[attr][type].indexOf(a)
})
item[attr][type] = item[attr][type].concat(val)
Expand All @@ -115,7 +115,7 @@ function applyAttributeUpdates (updates, table, item) {
if (item[attr] && !item[attr][type])
return db.validationError('Type mismatch for attribute to update')
if (item[attr] && item[attr][type]) {
item[attr][type] = item[attr][type].filter(function (val) { // eslint-disable-line no-loop-func
item[attr][type] = item[attr][type].filter(function (val) {
return !~update.Value[type].indexOf(val)
})
if (!item[attr][type].length) delete item[attr]
Expand Down Expand Up @@ -156,7 +156,7 @@ function applyUpdateExpression (sections, table, item) {
return db.validationError('An operand in the update expression has an incorrect data type')
}
if (alreadyExists) {
existing[section.attrType] = existing[section.attrType].filter(function (val) { // eslint-disable-line no-loop-func
existing[section.attrType] = existing[section.attrType].filter(function (val) {
return !~section.val[section.attrType].indexOf(val)
})
if (!existing[section.attrType].length) {
Expand All @@ -175,7 +175,7 @@ function applyUpdateExpression (sections, table, item) {
else {
if (!existing) existing = {}
if (!existing[section.attrType]) existing[section.attrType] = []
existing[section.attrType] = existing[section.attrType].concat(section.val[section.attrType].filter(function (a) { // eslint-disable-line no-loop-func
existing[section.attrType] = existing[section.attrType].concat(section.val[section.attrType].filter(function (a) {
return !~existing[section.attrType].indexOf(a)
}))
}
Expand Down
4 changes: 2 additions & 2 deletions actions/updateTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ module.exports = function updateTable (store, data, cb) {
}

tableDb.put(key, table, function (err) {
// eslint-disable-next-line no-console
if (err && !/Database is not open/.test(err)) console.error(err.stack || err)

if (err && !/Database is (not open|closed)/.test(err)) console.error(err.stack || err)
})

}, store.options.updateTableMs)
Expand Down
4 changes: 2 additions & 2 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
var argv = require('minimist')(process.argv.slice(2), { alias: { debug: [ 'd' ], verbose: [ 'v' ] } })

if (argv.help || argv.h) {
// eslint-disable-next-line no-console

return console.log([
'',
'Usage: dynalite [--port <port>] [--path <path>] [options]',
Expand Down Expand Up @@ -33,7 +33,7 @@ if (process.pid == 1) process.on('SIGINT', process.exit)
var server = require('./index.js')(argv)
.listen(argv.port || 4567, argv.host || undefined, function () {
var address = server.address(), protocol = argv.ssl ? 'https' : 'http'
// eslint-disable-next-line no-console

var host = argv.host || 'localhost'
console.log('Dynalite listening at: %s://%s:%s', protocol, host, address.port)
})
Loading
Loading