dynamodb-onetable
Advanced tools
Comparing version 0.7.1 to 0.7.2
@@ -6,3 +6,4 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Operators = ['<', '<=', '=', '>=', '>', 'begins', 'begins_with', 'between']; | ||
const KeyOperators = ['<', '<=', '=', '>=', '>', 'begins', 'begins_with', 'between']; | ||
const FilterOperators = ['<', '<=', '=', '<>', '>=', '>', 'begins', 'begins_with', 'between']; | ||
class Expression { | ||
@@ -209,3 +210,3 @@ /* | ||
let [action, vars] = Object.entries(value)[0]; | ||
if (Operators.indexOf(action) < 0) { | ||
if (FilterOperators.indexOf(action) < 0) { | ||
throw new Error(`Invalid FilterCondition operator "${action}"`); | ||
@@ -248,3 +249,3 @@ } | ||
let [action, vars] = Object.entries(value)[0]; | ||
if (Operators.indexOf(action) < 0) { | ||
if (KeyOperators.indexOf(action) < 0) { | ||
throw new Error(`Invalid KeyCondition operator "${action}"`); | ||
@@ -410,2 +411,10 @@ } | ||
} | ||
if (op == 'scan') { | ||
if (params.segments != null) { | ||
args.TotalSegments = params.segments; | ||
} | ||
if (params.segment != null) { | ||
args.Segment = params.segment; | ||
} | ||
} | ||
} | ||
@@ -412,0 +421,0 @@ args = Object.fromEntries(Object.entries(args).filter(([_, v]) => v != null)); |
@@ -48,2 +48,3 @@ export default class Model { | ||
decrypt(text: any, inCode?: string, outCode?: string): any; | ||
checkArgs(properties: any, params: any): void; | ||
} |
@@ -64,2 +64,11 @@ "use strict"; | ||
constructor(table, name, options = {}) { | ||
if (!table) { | ||
throw new Error('Missing table argument'); | ||
} | ||
if (!table.typeField || !table.ulid) { | ||
throw new Error('Invalid table instance'); | ||
} | ||
if (!name) { | ||
throw new Error('Missing name of model'); | ||
} | ||
this.table = table; | ||
@@ -332,2 +341,3 @@ this.name = name; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.checkArgs(properties, params); | ||
params = Object.assign({ parse: true, high: true, exists: false }, params); | ||
@@ -376,2 +386,3 @@ let result; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.checkArgs(properties, params); | ||
params = Object.assign({ parse: true, high: true }, params); | ||
@@ -383,2 +394,3 @@ return yield this.queryItems(properties, params); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.checkArgs(properties, params); | ||
params = Object.assign({ parse: true, high: true }, params); | ||
@@ -398,2 +410,3 @@ let expression = new Expression_js_1.default(this, 'get', properties, params); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.checkArgs(properties, params); | ||
params = Object.assign({ exists: null, high: true }, params); | ||
@@ -446,2 +459,3 @@ let expression = new Expression_js_1.default(this, 'delete', properties, params); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.checkArgs(properties, params); | ||
params = Object.assign({ parse: true, high: true }, params); | ||
@@ -500,3 +514,2 @@ properties = Object.assign({}, properties); | ||
} | ||
// Note: scanItems will return all model types | ||
/* private */ queryItems(properties = {}, params = {}) { | ||
@@ -588,8 +601,21 @@ return __awaiter(this, void 0, void 0, function* () { | ||
if (field.type == Date) { | ||
// Convert dates to unix epoch | ||
if (value instanceof Date) { | ||
value = value.getTime(); | ||
if (this.table.isoDates) { | ||
if (value instanceof Date) { | ||
value = value.toISOString(); | ||
} | ||
else if (typeof value == 'string') { | ||
value = (new Date(Date.parse(value))).toISOString(); | ||
} | ||
else if (typeof value == 'number') { | ||
value = (new Date(value)).toISOString(); | ||
} | ||
} | ||
else if (typeof value == 'string') { | ||
value = (new Date(Date.parse(value))).getTime(); | ||
else { | ||
// Convert dates to unix epoch | ||
if (value instanceof Date) { | ||
value = value.getTime(); | ||
} | ||
else if (typeof value == 'string') { | ||
value = (new Date(Date.parse(value))).getTime(); | ||
} | ||
} | ||
@@ -657,2 +683,5 @@ } | ||
} | ||
else if (field.ksuid) { | ||
value = this.table.ksuid(); | ||
} | ||
else if (field.uuid) { | ||
@@ -746,3 +775,11 @@ value = this.table.uuid(); | ||
} | ||
checkArgs(properties, params) { | ||
if (!properties) { | ||
throw new Error('Invalid properties'); | ||
} | ||
if (typeof params != 'object') { | ||
throw new Error('Invalid type for params'); | ||
} | ||
} | ||
} | ||
exports.default = Model; |
@@ -12,2 +12,3 @@ export default class Table { | ||
updatedField: any; | ||
isoDates: any; | ||
typeField: any; | ||
@@ -18,2 +19,3 @@ name: any; | ||
ulid(): string; | ||
ksuid: any; | ||
hidden: any; | ||
@@ -20,0 +22,0 @@ models: {}; |
@@ -31,4 +31,6 @@ "use strict"; | ||
delimiter, // Composite sort key delimiter (default ':'). | ||
hidden, // Hide key attributes in Javascript properties. Default false. | ||
isoDates, // Set to true to store dates as Javascript ISO Date strings. | ||
ksuid, // Function to create a KSUID if field schema requires it. | ||
logger, // Logging function(tag, message, properties). Tag is data.info|error|trace|exception. | ||
hidden, // Hide key attributes in Javascript properties. Default false. | ||
migrate, // Migration function(model, operation, data). Operation: 'create', 'delete', 'put', ... | ||
@@ -59,2 +61,3 @@ name, // Table name. | ||
this.updatedField = updatedField || 'updated'; | ||
this.isoDates = isoDates || false; | ||
this.typeField = typeField || '_type'; | ||
@@ -65,2 +68,3 @@ this.name = name; | ||
this.ulid = ulid || this.ulid; | ||
this.ksuid = ksuid; | ||
this.hidden = hidden || true; | ||
@@ -127,2 +131,8 @@ // Schema models | ||
let { models, indexes } = params; | ||
if (!models || typeof models != 'object') { | ||
throw new Error('Schema is missing models'); | ||
} | ||
if (!indexes || typeof indexes != 'object') { | ||
throw new Error('Schema is missing indexes'); | ||
} | ||
this.indexes = indexes; | ||
@@ -154,3 +164,5 @@ let migrate = params.migrate || this.migrate; | ||
removeModel(name) { | ||
delete this.models[name]; | ||
if (this.getModel(name)) { | ||
delete this.models[name]; | ||
} | ||
} | ||
@@ -347,11 +359,2 @@ /* | ||
} | ||
/* | ||
parseResponse(item, params) { | ||
item = this.unmarshall(item) | ||
let model = this.models[item[this.typeField]] | ||
if (model && model != this.unique) { | ||
item = model.mapReadData('get', item, params) | ||
} | ||
return item | ||
} */ | ||
log(type, message, context, params) { | ||
@@ -374,2 +377,3 @@ if (this.logger) { | ||
} | ||
// Simple time-based, sortable unique ID. | ||
ulid() { | ||
@@ -376,0 +380,0 @@ return new ULID_js_1.default().toString(); |
/* | ||
Expression.js - DynamoDB API command builder | ||
*/ | ||
const Operators = ['<', '<=', '=', '>=', '>', 'begins', 'begins_with', 'between']; | ||
const KeyOperators = ['<', '<=', '=', '>=', '>', 'begins', 'begins_with', 'between']; | ||
const FilterOperators = ['<', '<=', '=', '<>', '>=', '>', 'begins', 'begins_with', 'between']; | ||
export default class Expression { | ||
@@ -206,3 +207,3 @@ /* | ||
let [action, vars] = Object.entries(value)[0]; | ||
if (Operators.indexOf(action) < 0) { | ||
if (FilterOperators.indexOf(action) < 0) { | ||
throw new Error(`Invalid FilterCondition operator "${action}"`); | ||
@@ -245,3 +246,3 @@ } | ||
let [action, vars] = Object.entries(value)[0]; | ||
if (Operators.indexOf(action) < 0) { | ||
if (KeyOperators.indexOf(action) < 0) { | ||
throw new Error(`Invalid KeyCondition operator "${action}"`); | ||
@@ -407,2 +408,10 @@ } | ||
} | ||
if (op == 'scan') { | ||
if (params.segments != null) { | ||
args.TotalSegments = params.segments; | ||
} | ||
if (params.segment != null) { | ||
args.Segment = params.segment; | ||
} | ||
} | ||
} | ||
@@ -409,0 +418,0 @@ args = Object.fromEntries(Object.entries(args).filter(([_, v]) => v != null)); |
@@ -48,2 +48,3 @@ export default class Model { | ||
decrypt(text: any, inCode?: string, outCode?: string): any; | ||
checkArgs(properties: any, params: any): void; | ||
} |
@@ -50,2 +50,11 @@ /* | ||
constructor(table, name, options = {}) { | ||
if (!table) { | ||
throw new Error('Missing table argument'); | ||
} | ||
if (!table.typeField || !table.ulid) { | ||
throw new Error('Invalid table instance'); | ||
} | ||
if (!name) { | ||
throw new Error('Missing name of model'); | ||
} | ||
this.table = table; | ||
@@ -315,2 +324,3 @@ this.name = name; | ||
async create(properties, params = {}) { | ||
this.checkArgs(properties, params); | ||
params = Object.assign({ parse: true, high: true, exists: false }, params); | ||
@@ -355,2 +365,3 @@ let result; | ||
async find(properties = {}, params = {}) { | ||
this.checkArgs(properties, params); | ||
params = Object.assign({ parse: true, high: true }, params); | ||
@@ -360,2 +371,3 @@ return await this.queryItems(properties, params); | ||
async get(properties, params = {}) { | ||
this.checkArgs(properties, params); | ||
params = Object.assign({ parse: true, high: true }, params); | ||
@@ -373,2 +385,3 @@ let expression = new Expression(this, 'get', properties, params); | ||
async remove(properties, params = {}) { | ||
this.checkArgs(properties, params); | ||
params = Object.assign({ exists: null, high: true }, params); | ||
@@ -415,2 +428,3 @@ let expression = new Expression(this, 'delete', properties, params); | ||
async scan(properties = {}, params = {}) { | ||
this.checkArgs(properties, params); | ||
params = Object.assign({ parse: true, high: true }, params); | ||
@@ -458,3 +472,2 @@ properties = Object.assign({}, properties); | ||
} | ||
// Note: scanItems will return all model types | ||
/* private */ async queryItems(properties = {}, params = {}) { | ||
@@ -540,8 +553,21 @@ let expression = new Expression(this, 'find', properties, params); | ||
if (field.type == Date) { | ||
// Convert dates to unix epoch | ||
if (value instanceof Date) { | ||
value = value.getTime(); | ||
if (this.table.isoDates) { | ||
if (value instanceof Date) { | ||
value = value.toISOString(); | ||
} | ||
else if (typeof value == 'string') { | ||
value = (new Date(Date.parse(value))).toISOString(); | ||
} | ||
else if (typeof value == 'number') { | ||
value = (new Date(value)).toISOString(); | ||
} | ||
} | ||
else if (typeof value == 'string') { | ||
value = (new Date(Date.parse(value))).getTime(); | ||
else { | ||
// Convert dates to unix epoch | ||
if (value instanceof Date) { | ||
value = value.getTime(); | ||
} | ||
else if (typeof value == 'string') { | ||
value = (new Date(Date.parse(value))).getTime(); | ||
} | ||
} | ||
@@ -609,2 +635,5 @@ } | ||
} | ||
else if (field.ksuid) { | ||
value = this.table.ksuid(); | ||
} | ||
else if (field.uuid) { | ||
@@ -698,2 +727,10 @@ value = this.table.uuid(); | ||
} | ||
checkArgs(properties, params) { | ||
if (!properties) { | ||
throw new Error('Invalid properties'); | ||
} | ||
if (typeof params != 'object') { | ||
throw new Error('Invalid type for params'); | ||
} | ||
} | ||
} |
@@ -12,2 +12,3 @@ export default class Table { | ||
updatedField: any; | ||
isoDates: any; | ||
typeField: any; | ||
@@ -18,2 +19,3 @@ name: any; | ||
ulid(): string; | ||
ksuid: any; | ||
hidden: any; | ||
@@ -20,0 +22,0 @@ models: {}; |
@@ -17,4 +17,6 @@ /* | ||
delimiter, // Composite sort key delimiter (default ':'). | ||
hidden, // Hide key attributes in Javascript properties. Default false. | ||
isoDates, // Set to true to store dates as Javascript ISO Date strings. | ||
ksuid, // Function to create a KSUID if field schema requires it. | ||
logger, // Logging function(tag, message, properties). Tag is data.info|error|trace|exception. | ||
hidden, // Hide key attributes in Javascript properties. Default false. | ||
migrate, // Migration function(model, operation, data). Operation: 'create', 'delete', 'put', ... | ||
@@ -45,2 +47,3 @@ name, // Table name. | ||
this.updatedField = updatedField || 'updated'; | ||
this.isoDates = isoDates || false; | ||
this.typeField = typeField || '_type'; | ||
@@ -51,2 +54,3 @@ this.name = name; | ||
this.ulid = ulid || this.ulid; | ||
this.ksuid = ksuid; | ||
this.hidden = hidden || true; | ||
@@ -113,2 +117,8 @@ // Schema models | ||
let { models, indexes } = params; | ||
if (!models || typeof models != 'object') { | ||
throw new Error('Schema is missing models'); | ||
} | ||
if (!indexes || typeof indexes != 'object') { | ||
throw new Error('Schema is missing indexes'); | ||
} | ||
this.indexes = indexes; | ||
@@ -140,3 +150,5 @@ let migrate = params.migrate || this.migrate; | ||
removeModel(name) { | ||
delete this.models[name]; | ||
if (this.getModel(name)) { | ||
delete this.models[name]; | ||
} | ||
} | ||
@@ -303,11 +315,2 @@ /* | ||
} | ||
/* | ||
parseResponse(item, params) { | ||
item = this.unmarshall(item) | ||
let model = this.models[item[this.typeField]] | ||
if (model && model != this.unique) { | ||
item = model.mapReadData('get', item, params) | ||
} | ||
return item | ||
} */ | ||
log(type, message, context, params) { | ||
@@ -330,2 +333,3 @@ if (this.logger) { | ||
} | ||
// Simple time-based, sortable unique ID. | ||
ulid() { | ||
@@ -332,0 +336,0 @@ return new ULID().toString(); |
{ | ||
"name": "dynamodb-onetable", | ||
"version": "0.7.1", | ||
"description": "DynamoDB OneTable", | ||
"version": "0.7.2", | ||
"description": "DynamoDB access library for single-table designs", | ||
"exports": { | ||
@@ -6,0 +6,0 @@ ".": { |
@@ -279,3 +279,3 @@ # DynamoDB OneTable | ||
// Fetch an Account using the Account model | ||
let account = table.find('Account', {id}) | ||
let account = await table.find('Account', {id}) | ||
``` | ||
@@ -293,4 +293,6 @@ | ||
| delimiter | `string` | Composite sort key delimiter (default ':'). | | ||
| hidden | `boolean` | Hide key attributes in Javascript properties. Default true. | | ||
| isoDates | `boolean` | Set to true to store dates as Javascript ISO strings vs epoch numerics. Default false. | | ||
| ksuid | `string` | Function to create a KSUID if field schema requires it. No default internal implementation is provided. | | ||
| logger | `object` | Logging function(tag, message, properties). Tag is data.info|error|trace|exception. | | ||
| hidden | `boolean` | Hide key attributes in Javascript properties. Default true. | | ||
| name | `string` | yes | The name of your DynamoDB table. | | ||
@@ -302,4 +304,4 @@ | nulls | `boolean` | Store nulls in database attributes. Default false. | | ||
| updatedField | `string` | Name of the "updated" timestamp attribute. Default "updated". | | ||
| ulid | `string` | Function to create a ULID if field schema requires it. | | ||
| uuid | `string` | Function to create a UUID if field schema requires it. | | ||
| ulid | `string` | Function to create a ULID if field schema requires it. If not defined, internal implementation is used. | | ||
| uuid | `string` | Function to create a UUID if field schema requires it. If not defined, internal implementation is used. | | ||
@@ -402,2 +404,3 @@ The `client` property must be an initialized [AWS DocumentClient](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html). The DocumentClient API is currently supported by the AWS v2 API. The recently released AWS v3 API does not yet support the DocumentClient API (stay tuned - See [Issue](https://github.com/sensedeep/dynamodb-onetable/issues/2)). | ||
| hidden | `boolean` | Set to true to omit the attribute in the returned Javascript results. | | ||
| ksuid | `boolean` | Set to true to automatically create a new KSUID (time-based sortable unique string) for the attribute when creating. Default false. This requires an implementation be passed to the Table constructor. | | ||
| map | `string` | Map the field value to a different attribute when storing in the database. | | ||
@@ -436,3 +439,3 @@ | nulls | `boolean` | Set to true to store null values. Default false. | | ||
The `value` property defines a literal string template that is used to compute the attribute value. The `value` is a template string that may contain `${name}` references to other model attributes. This is useful for computing key values from other attributes and for creating compound (composite) sort keys. | ||
The `value` property defines a literal string template, similar to JavaScript string templates, that is used to compute the attribute value. The template string may contain `${name}` references to other model attributes. This is useful for computing key values from other attributes and for creating compound (composite) sort keys. | ||
@@ -594,2 +597,4 @@ ### Table Contexts | ||
For non-key attributes, you can also use '<>' for not equals. | ||
Some useful params for queryItems include: | ||
@@ -632,3 +637,15 @@ | ||
The scan method supports parallel scan where you invoke scan simultaneously from multiple workers. Using the async/await pattern, you can start the workers and then use a Promise.all to wait for their completion. | ||
To perform parallel scans, you should set the `params.segments` to the number of parallel segements and the `params.segment` to the numeric segment to be scaned for that worker. | ||
```javacript | ||
const segments = 4 | ||
let promises = [] | ||
for (let segment = 0; segment < segments; segment++) { | ||
promises.push(table.scanItems({}, {segment, segments})) | ||
} | ||
let results = await Promise.all(promises) | ||
``` | ||
#### setContext(context = {}, merge = false) | ||
@@ -720,9 +737,9 @@ | ||
let account = await Account.get({name: 'Acme Airplanes'}) | ||
let user = User.get({email: 'user@example.com'}, {index: 'gs1'}) | ||
let user = await User.get({email: 'user@example.com'}, {index: 'gs1'}) | ||
// find (query) items | ||
let users = User.find({accountName: 'Acme Airplanes'}) | ||
let users = await User.find({accountName: 'Acme Airplanes'}) | ||
// Update an item | ||
let user = User.update({email: 'user@example.com', balance: 0}, {index: 'gs1'}) | ||
let user = await User.update({email: 'user@example.com', balance: 0}, {index: 'gs1'}) | ||
``` | ||
@@ -818,3 +835,3 @@ | ||
If the `params.follow` is set to true, each item will be re-fetched using the returned results. This is useful for KEYS_ONLY secondary indexes where OneTable will use the retrieved keys to fetch all the attributes of the entire item using the primary index. This incurs an additional request for each item, but for very large data sets, it enables the transparent use of a KEYS_ONLY secondary index which reduces the size of the database. | ||
If the `params.follow` is set to true, each item will be re-fetched using the returned results. This is useful for KEYS_ONLY secondary indexes where OneTable will use the retrieved keys to fetch all the attributes of the entire item using the primary index. This incurs an additional request for each item, but for very large data sets, it enables the transparent use of a KEYS_ONLY secondary index which may greatly reduce the size (and cost) of the secondary index. | ||
@@ -867,4 +884,6 @@ <a name="model-get"></a> | ||
Scan items in the database and return items of the given model type. This wraps the DynamoDB `scan` method. Use `scanItems` to return all model types. | ||
Scan items in the database and return items of the given model type. This wraps the DynamoDB `scan` method and uses a filter expression to extract the designated model type. Use `scanItems` to return all model types. NOTE: this will still scan the entire database. | ||
An alternative to using scan to retrieve all items of a give model type is to create a GSI and index the model `type` field and then use `query` to retrieve the items. This index can be a sparse index if only a subset of models are indexed. | ||
The `properties` parameter is a Javascript hash containing fields used to construct a filter expression which is applied by DynamoDB after reading the data but before returning it to the caller. OneTable will utilize fields in `properties` that correspond to the schema attributes for the model. Superfluous property fields will be ignored in the filter expression. | ||
@@ -884,3 +903,15 @@ | ||
The scan method supports parallel scan where you invoke scan simultaneously from multiple workers. Using the async/await pattern, you can start the workers and then use a Promise.all to wait for their completion. | ||
To perform parallel scans, you should set the `params.segments` to the number of parallel segements and the `params.segment` to the numeric segment to be scaned for that worker. | ||
```javacript | ||
const segments = 4 | ||
let promises = [] | ||
for (let segment = 0; segment < segments; segment++) { | ||
promises.push(table.scan({}, {segment, segments})) | ||
} | ||
let results = await Promise.all(promises) | ||
``` | ||
<a name="model-update"></a> | ||
@@ -972,3 +1003,3 @@ #### async update(properties, params = {}) | ||
Where clauses when used with `find` or `scan` can also use the `<>` not equals operator. | ||
Where clauses when used with `find` or `scan` on non-key attribugtes can also use the `<>` not equals operator. | ||
@@ -975,0 +1006,0 @@ See the [AWS Comparison Expression Reference](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html) for more details. |
208845
4025
1018