dynoquery 0.1.25 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -66,7 +66,7 @@ async function userExample() {
66
66
 
67
67
  #### Second-Level Objects (Single-Row Operations)
68
68
 
69
- For a more flexible way to work with individual rows, you can use `draft()` and `get()` to obtain an `Item` object, then call `create()`, `update()`, or `save()` on it directly. You can also use a shorthand by passing a second argument to your model function (as shown in the examples below).
69
+ For a more flexible way to work with individual rows, you can use `draft()` and `get()` to obtain an `Item` object, then call `save()` on it directly. You can also use a shorthand by passing a second argument to your model function (as shown in the examples below).
70
70
 
71
71
  ```typescript
72
72
  const john = db.User('john@example.com');
@@ -78,8 +78,8 @@ johnMeta.email = 'johndoe@johnmail.com';
78
78
  await johnMeta.save();
79
79
 
80
80
  // 2. Create with properties in arguments
81
- const johnStat = john.draft('STAT');
82
- await johnStat.create({ views: 100 });
81
+ const johnStat = john.draft('STAT', { views: 100 });
82
+ await johnStat.save();
83
83
 
84
84
  // 3. Get and update an existing row
85
85
  const johnBio = await john.get('BIO');
@@ -87,8 +87,7 @@ johnBio.birthYear = 1986;
87
87
  await johnBio.save();
88
88
 
89
89
  // 4. Partial update without fetching
90
- const johnFriend1 = john.draft('FRIEND#1');
91
- await johnFriend1.update({ Name: 'Alice', rank: 1 });
90
+ await john.update('FRIEND#1', { Name: 'Alice', rank: 1 });
92
91
 
93
92
  // 5. Set properties during initialization
94
93
  const johnPref = john.draft('PREF', { theme: 'dark' });
@@ -96,7 +95,8 @@ await johnPref.save();
96
95
 
97
96
  // 6. Shorthand access (returns an Item object directly)
98
97
  const johnMeta = db.User('john@example.com', 'METADATA');
99
- await johnMeta.update({ name: 'John Doe' });
98
+ johnMeta.name = 'John Doe';
99
+ await johnMeta.save();
100
100
  ```
101
101
 
102
102
  ### 2. Global Secondary Indexes (findBy)
@@ -137,7 +137,7 @@ async function indexExample() {
137
137
 
138
138
  ### 3. Creating Items with GSI Support
139
139
 
140
- You can pass index queries directly to `create()` or `update()` to automatically populate GSI attributes. This works on both the Partition level and the Item level.
140
+ You can pass index queries directly to `create()` or `update()` to automatically populate GSI attributes. This works on the Partition level.
141
141
 
142
142
  #### Using Partition.create() and Partition.update()
143
143
  ```typescript
@@ -153,23 +153,8 @@ await db.Product('p123').create('INFO', {
153
153
  await db.Product('p123').update('INFO', { price: 45 }, [electronics]);
154
154
  ```
155
155
 
156
- #### Using Item.create() and Item.update()
157
- ```typescript
158
- const electronics = db.findByCategory('ELECTRONICS', 'RANK#1');
159
- const mouse = db.Product('p123', 'INFO');
160
-
161
- // Pass data and indices directly to create()
162
- await mouse.create({
163
- name: 'Gaming Mouse',
164
- price: 50
165
- }, [electronics]);
166
-
167
- // Or use update() directly on the item for partial updates
168
- await mouse.update({ price: 40 }, [electronics]);
169
- ```
170
-
171
156
  #### Using setIndex() for persistence
172
- You can also use `setIndex()` to attach indices to an item so they are used automatically whenever you call `save()`.
157
+ You can also use `setIndex()` to attach indices to an item so they are used automatically whenever you call `save()`. This is useful when you have an Item object (e.g., from `draft()` or `get()`).
173
158
 
174
159
  ```typescript
175
160
  const electronics = db.findByCategory('ELECTRONICS', 'RANK#1');
@@ -242,6 +227,31 @@ if (items[0]) {
242
227
  }
243
228
  ```
244
229
 
230
+ ### 6. Time To Live (TTL)
231
+
232
+ DynamoDB TTL allows you to automatically delete items after a certain timestamp. To use it in DynoQuery, configure `ttlAttributeName` when initializing the client.
233
+
234
+ > **Note:** DynoQuery does not enable TTL in DynamoDB. It only sets the values using the `ttl()` function. You must manually enable TTL for your table in the AWS Console or via CLI/CloudFormation first.
235
+
236
+ ```typescript
237
+ const db = new DynoQuery({
238
+ region: 'us-east-1',
239
+ tableName: 'MyTable',
240
+ ttlAttributeName: 'expireAt' // The attribute name which is set in DynamoDB to be used for TTL
241
+ });
242
+
243
+ async function ttlExample() {
244
+ const session = db.User('john@example.com').draft('SESSION');
245
+
246
+ // Set TTL to 1 hour from now (timestamp in seconds)
247
+ const ttl = Math.floor(Date.now() / 1000) + 3600;
248
+ session.ttl(ttl);
249
+
250
+ session.data = 'some session data';
251
+ await session.save();
252
+ }
253
+ ```
254
+
245
255
  ## Optional Configuration Parameters
246
256
 
247
257
  | Parameter | Type | Default | Description |
@@ -249,6 +259,7 @@ if (items[0]) {
249
259
  | `pkName` | `string` | `'PK'` | Custom attribute name for Partition Key. |
250
260
  | `skName` | `string` | `'SK'` | Custom attribute name for Sort Key. |
251
261
  | `pkPrefix` | `string` | `''` | Global prefix for all partitions (useful for multitenancy, e.g., `TENANT#A#`). |
262
+ | `ttlAttributeName` | `string` | - | Optional attribute name for DynamoDB TTL. |
252
263
  | `endpoint` | `string` | - | Optional endpoint for local development (e.g., `http://localhost:8000`). |
253
264
  | `credentials` | `object` | - | Custom AWS credentials (`{ accessKeyId, secretAccessKey, sessionToken? }`). |
254
265
 
@@ -277,12 +288,12 @@ Both `Partition.getAll()` and `IndexQuery.getAll()` support pagination.
277
288
 
278
289
  ```typescript
279
290
  const index = db.findByCategory('ELECTRONICS');
280
- const items = await index.getAll({ limit: 10 });
291
+ const items = await index.getAll({ Limit: 10 });
281
292
 
282
293
  const token = index.getLastEvaluatedKey();
283
294
  if (token) {
284
295
  // Fetch next page
285
- const nextItems = await index.getAll({ limit: 10, exclusiveStartKey: token });
296
+ const nextItems = await index.getAll({ Limit: 10, ExclusiveStartKey: token });
286
297
  }
287
298
  ```
288
299
 
@@ -353,7 +364,7 @@ await johnMeta.save();
353
364
 
354
365
  ### Partition
355
366
  - `get(skValue)`: Fetches data for a specific Sort Key value (returns a Promise).
356
- - `getAll(options?)`: Fetches items in the partition. Options: `{ limit, exclusiveStartKey, filterBuilder, FilterExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
367
+ - `getAll(options?)`: Fetches items in the partition. Options: `{ Limit, ExclusiveStartKey, filterBuilder, FilterExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
357
368
  - `create(skValue, data, indices?, options?)`: Creates an item. `options`: `{ conditionBuilder, ConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
358
369
  - `update(skValue, data, indices?, options?)`: Partial update of an item. `options`: `{ conditionBuilder, ConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
359
370
  - `delete(skValue, options?)`: Deletes an item. `options`: `{ conditionBuilder, ConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
@@ -364,13 +375,12 @@ await johnMeta.save();
364
375
 
365
376
  ### IndexQuery
366
377
  - `get(skValue?)`: Get a single item from the index.
367
- - `getAll(options?)`: Query index. Options: `{ limit, scanIndexForward, exclusiveStartKey, skValue, filterBuilder, FilterExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
378
+ - `getAll(options?)`: Query index. Options: `{ Limit, ScanIndexForward, ExclusiveStartKey, skValue, filterBuilder, FilterExpression, ExpressionAttributeNames, ExpressionAttributeValues }`.
368
379
  - `getLastEvaluatedKey()`: Returns the pagination token from the last `getAll()`.
369
380
 
370
381
  ### Item (returned by Partition.get or draft)
371
- - `create(data?, indices?)`: Persists the item as a new record with the provided data. Supports GSI indices and internal `conditionBuilder`.
372
- - `update(data, indices?)`: Partial update of the item. Supports GSI indices and internal `conditionBuilder`.
373
382
  - `save()`: Persists the current state of the item. Uses indices attached via `setIndex()` and internal `conditionBuilder`.
383
+ - `ttl(timestamp)`: Sets the TTL attribute value. Only works if `ttlAttributeName` is configured in `DynoQueryConfig`.
374
384
  - `getData()`: Returns a clean data object containing only the database attributes (filters out internal state and methods).
375
385
  - `setIndex(indices)`: Attaches one or more `IndexQuery` objects to the item.
376
386
  - `setFilter(builder)`: Sets a filter expression builder for the item.
@@ -17,12 +17,12 @@ export declare class IndexQuery {
17
17
  protected skName: string;
18
18
  protected pkValue: string;
19
19
  protected skValue?: string;
20
- protected lastEvaluatedKey: any;
20
+ protected LastEvaluatedKey: any;
21
21
  constructor(db: DynoQuery, config: IndexQueryConfig);
22
22
  getAll<T = any>(options?: {
23
- limit?: number;
24
- scanIndexForward?: boolean;
25
- exclusiveStartKey?: any;
23
+ Limit?: number;
24
+ ScanIndexForward?: boolean;
25
+ ExclusiveStartKey?: any;
26
26
  skValue?: string;
27
27
  filterBuilder?: ExpressionBuilder;
28
28
  FilterExpression?: string;
@@ -12,7 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.IndexQuery = void 0;
13
13
  class IndexQuery {
14
14
  constructor(db, config) {
15
- this.lastEvaluatedKey = null;
15
+ this.LastEvaluatedKey = null;
16
16
  this.db = db;
17
17
  this.tableName = config.tableName || db.getTableName() || "";
18
18
  this.indexName = config.indexName;
@@ -55,19 +55,19 @@ class IndexQuery {
55
55
  FilterExpression: filterExpression,
56
56
  ExpressionAttributeNames: expressionAttributeNames,
57
57
  ExpressionAttributeValues: expressionAttributeValues,
58
- Limit: options === null || options === void 0 ? void 0 : options.limit,
59
- ScanIndexForward: options === null || options === void 0 ? void 0 : options.scanIndexForward,
60
- ExclusiveStartKey: options === null || options === void 0 ? void 0 : options.exclusiveStartKey,
58
+ Limit: options === null || options === void 0 ? void 0 : options.Limit,
59
+ ScanIndexForward: options === null || options === void 0 ? void 0 : options.ScanIndexForward,
60
+ ExclusiveStartKey: options === null || options === void 0 ? void 0 : options.ExclusiveStartKey,
61
61
  });
62
62
  const items = (response.Items || []);
63
63
  const mappedItems = items.map(item => this.db.mapItemToModelItem(item));
64
- this.lastEvaluatedKey = response.LastEvaluatedKey || null;
64
+ this.LastEvaluatedKey = response.LastEvaluatedKey || null;
65
65
  return mappedItems;
66
66
  });
67
67
  }
68
68
  get(skValue) {
69
69
  return __awaiter(this, void 0, void 0, function* () {
70
- const items = yield this.getAll({ limit: 1, skValue });
70
+ const items = yield this.getAll({ Limit: 1, skValue });
71
71
  return items.length > 0 ? items[0] : null;
72
72
  });
73
73
  }
@@ -84,7 +84,7 @@ class IndexQuery {
84
84
  return this.skValue;
85
85
  }
86
86
  getLastEvaluatedKey() {
87
- return this.lastEvaluatedKey;
87
+ return this.LastEvaluatedKey;
88
88
  }
89
89
  }
90
90
  exports.IndexQuery = IndexQuery;
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export interface DynoQueryConfig {
10
10
  region?: string;
11
11
  endpoint?: string;
12
12
  pkPrefix?: string;
13
+ ttlAttributeName?: string;
13
14
  credentials?: {
14
15
  accessKeyId: string;
15
16
  secretAccessKey: string;
@@ -32,6 +33,7 @@ export declare class DynoQuery {
32
33
  private globalPkPrefix;
33
34
  private pkName;
34
35
  private skName;
36
+ private ttlAttributeName?;
35
37
  private registeredModels;
36
38
  [key: string]: any;
37
39
  constructor(config?: DynoQueryConfig);
@@ -83,6 +85,7 @@ export declare class DynoQuery {
83
85
  getPkPrefix(): string;
84
86
  getPkName(): string;
85
87
  getSkName(): string;
88
+ getTtlAttributeName(): string | undefined;
86
89
  getRegisteredModels(): Record<string, {
87
90
  pkPrefix: string;
88
91
  }>;
package/dist/index.js CHANGED
@@ -45,7 +45,7 @@ Object.defineProperty(exports, "attr", { enumerable: true, get: function () { re
45
45
  class DynoQuery {
46
46
  constructor(config = {}) {
47
47
  this.registeredModels = {};
48
- const { tableName, pkName, skName, pkPrefix, models, findBy } = config, clientConfig = __rest(config, ["tableName", "pkName", "skName", "pkPrefix", "models", "findBy"]);
48
+ const { tableName, pkName, skName, pkPrefix, ttlAttributeName, models, findBy } = config, clientConfig = __rest(config, ["tableName", "pkName", "skName", "pkPrefix", "ttlAttributeName", "models", "findBy"]);
49
49
  this.client = new client_dynamodb_1.DynamoDBClient(clientConfig);
50
50
  this.docClient = lib_dynamodb_1.DynamoDBDocumentClient.from(this.client, {
51
51
  marshallOptions: {
@@ -56,6 +56,7 @@ class DynoQuery {
56
56
  this.globalPkPrefix = pkPrefix || "";
57
57
  this.pkName = pkName || "PK";
58
58
  this.skName = skName || "SK";
59
+ this.ttlAttributeName = ttlAttributeName;
59
60
  if (models) {
60
61
  this.registeredModels = models;
61
62
  Object.entries(models).forEach(([name, def]) => {
@@ -423,6 +424,9 @@ class DynoQuery {
423
424
  getSkName() {
424
425
  return this.skName;
425
426
  }
427
+ getTtlAttributeName() {
428
+ return this.ttlAttributeName;
429
+ }
426
430
  getRegisteredModels() {
427
431
  return this.registeredModels;
428
432
  }
@@ -24,15 +24,16 @@ export declare class Partition {
24
24
  protected skName: string;
25
25
  protected cache: Record<string, any>;
26
26
  protected isLoaded: boolean;
27
- protected lastEvaluatedKey: any;
27
+ protected LastEvaluatedKey: any;
28
+ protected ttlAttributeName?: string;
28
29
  constructor(db: DynoQuery, config: PartitionConfig, id?: string);
29
30
  /**
30
31
  * Fetches all items in the partition and caches them.
31
32
  * Returns the data and caches it.
32
33
  */
33
34
  getAll<T = any>(options?: {
34
- limit?: number;
35
- exclusiveStartKey?: any;
35
+ Limit?: number;
36
+ ExclusiveStartKey?: any;
36
37
  filterBuilder?: ExpressionBuilder;
37
38
  FilterExpression?: string;
38
39
  ExpressionAttributeNames?: Record<string, string>;
@@ -86,6 +87,7 @@ export declare class Partition {
86
87
  draftDelete<T = any>(skValue: string): T;
87
88
  getTableName(): string;
88
89
  getPkValue(): string;
90
+ getTtlAttributeName(): string | undefined;
89
91
  getLastEvaluatedKey(): any;
90
92
  /**
91
93
  * Delete all data in this partition.
package/dist/partition.js CHANGED
@@ -47,22 +47,6 @@ class Item {
47
47
  });
48
48
  };
49
49
  }
50
- if (prop === "create") {
51
- return (data, indices) => {
52
- const dataToSave = data || {};
53
- const finalIndices = indices || self._indices;
54
- return partition.create(skValue, dataToSave, finalIndices, {
55
- conditionBuilder: self._conditionBuilder,
56
- });
57
- };
58
- }
59
- if (prop === "update") {
60
- return (data, indices) => {
61
- return partition.update(skValue, data, indices, {
62
- conditionBuilder: self._conditionBuilder,
63
- });
64
- };
65
- }
66
50
  if (prop === "setFilter") {
67
51
  return (builder) => {
68
52
  self._filterBuilder = builder;
@@ -92,6 +76,15 @@ class Item {
92
76
  return receiver;
93
77
  };
94
78
  }
79
+ if (prop === "ttl") {
80
+ return (timestamp) => {
81
+ const ttlAttr = partition.getTtlAttributeName();
82
+ if (ttlAttr) {
83
+ self[ttlAttr] = timestamp;
84
+ }
85
+ return receiver;
86
+ };
87
+ }
95
88
  if (prop === "getPartition") {
96
89
  return () => partition;
97
90
  }
@@ -111,11 +104,12 @@ class Partition {
111
104
  constructor(db, config, id) {
112
105
  this.cache = {};
113
106
  this.isLoaded = false;
114
- this.lastEvaluatedKey = null;
107
+ this.LastEvaluatedKey = null;
115
108
  this.db = db;
116
109
  this.tableName = config.tableName || db.getTableName();
117
110
  this.pkName = db.getPkName();
118
111
  this.skName = db.getSkName();
112
+ this.ttlAttributeName = db.getTtlAttributeName();
119
113
  if (config.pk) {
120
114
  this.pkValue = config.pk;
121
115
  }
@@ -169,8 +163,8 @@ class Partition {
169
163
  FilterExpression: filterExpression,
170
164
  ExpressionAttributeNames: expressionAttributeNames,
171
165
  ExpressionAttributeValues: expressionAttributeValues,
172
- Limit: options === null || options === void 0 ? void 0 : options.limit,
173
- ExclusiveStartKey: options === null || options === void 0 ? void 0 : options.exclusiveStartKey,
166
+ Limit: options === null || options === void 0 ? void 0 : options.Limit,
167
+ ExclusiveStartKey: options === null || options === void 0 ? void 0 : options.ExclusiveStartKey,
174
168
  });
175
169
  const items = (response.Items || []);
176
170
  items.forEach((item) => {
@@ -178,10 +172,10 @@ class Partition {
178
172
  this.cache[item[this.skName]] = item;
179
173
  }
180
174
  });
181
- if (!(options === null || options === void 0 ? void 0 : options.exclusiveStartKey) && !response.LastEvaluatedKey) {
175
+ if (!(options === null || options === void 0 ? void 0 : options.ExclusiveStartKey) && !response.LastEvaluatedKey) {
182
176
  this.isLoaded = true;
183
177
  }
184
- this.lastEvaluatedKey = response.LastEvaluatedKey || null;
178
+ this.LastEvaluatedKey = response.LastEvaluatedKey || null;
185
179
  return items.map((item) => new Item(this, item[this.skName], item));
186
180
  });
187
181
  }
@@ -327,8 +321,11 @@ class Partition {
327
321
  getPkValue() {
328
322
  return this.pkValue;
329
323
  }
324
+ getTtlAttributeName() {
325
+ return this.ttlAttributeName;
326
+ }
330
327
  getLastEvaluatedKey() {
331
- return this.lastEvaluatedKey;
328
+ return this.LastEvaluatedKey;
332
329
  }
333
330
  /**
334
331
  * Delete all data in this partition.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynoquery",
3
- "version": "0.1.25",
3
+ "version": "0.2.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/devspikejs/dynoquery.git"