dynoquery 0.1.4 → 0.1.5

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
@@ -31,9 +31,10 @@ const db = new DynoQuery({
31
31
  // optional endpoint for local development
32
32
  // endpoint: 'http://localhost:8000'
33
33
  partitions: {
34
- User: { pkPrefix: 'USER#' },
34
+ User: { pkPrefix: 'USER#' }, // TENANT#A#USER#
35
35
  },
36
36
  indexes: {
37
+ // TENANT#A#CAT#
37
38
  ByCategory: { indexName: 'GSI1', pkPrefix: 'CAT#' } // pkName defaults to GSI1PK, skName defaults to GSI1SK
38
39
  }
39
40
  });
@@ -68,6 +69,16 @@ async function example() {
68
69
  // Create an item through partition
69
70
  const profileModel = await john.create('PROFILE', { name: 'John Doe', email: 'john@example.com' });
70
71
 
72
+ // You can also use getPkValue() to get the generated PK for the partition or index
73
+ // This is useful when you need to store it in another attribute (e.g., GSI)
74
+ const cat = db.ByCategory('USER');
75
+ const profileWithGSI = await john.create('PROFILE', {
76
+ name: 'John Doe',
77
+ email: 'john@example.com',
78
+ GSI1PK: cat.getPkValue(),
79
+ GSI1SK: 'john@example.com'
80
+ });
81
+
71
82
  // Update the model (updates both DB and partition cache)
72
83
  await profileModel.update({ theme: 'dark' });
73
84
 
@@ -88,6 +99,48 @@ async function example() {
88
99
  }
89
100
  ```
90
101
 
102
+ ### Batch Operations
103
+
104
+ If you have a `tableName` configured in `DynoQuery`, you can use the `Items` property in `batchGet` and `batchWrite` to automatically target that table.
105
+
106
+ You can also generate items for `batchGet` using the `batchGetInput` method from partitions and indexes:
107
+
108
+ ```typescript
109
+ const john = db.User('john@example.com');
110
+ const jack = db.User('jack@example.com');
111
+ const cat = db.ByCategory('1');
112
+
113
+ const batchItem1 = john.batchGetInput('METADATA');
114
+ const batchOthers = cat.batchGetInput('METADATA', 'DATA', 'PROFILE');
115
+ const batchCat = cat.batchGetInput(); // No sk defined, so will get partition key only
116
+
117
+ await db.batchGet(batchItem1, batchOthers, batchCat);
118
+ ```
119
+
120
+ ```typescript
121
+ // batchGet with explicit Items
122
+ await db.batchGet({
123
+ Items: [
124
+ { PK: 'USER#1', SK: 'METADATA' },
125
+ { PK: 'USER#2', SK: 'METADATA' }
126
+ ]
127
+ });
128
+
129
+ // batchWrite with simplified Items (automatically wrapped in PutRequest)
130
+ await db.batchWrite({
131
+ Items: [
132
+ { PK: 'USER#3', SK: 'METADATA', name: 'Alice' },
133
+ {
134
+ DeleteRequest: {
135
+ Key: { PK: 'USER#1', SK: 'SESSION#123' }
136
+ }
137
+ }
138
+ ]
139
+ });
140
+ ```
141
+
142
+ You can still use the standard AWS SDK `RequestItems` if you need to target multiple tables or if you prefer the original syntax.
143
+
91
144
  ## API Reference
92
145
 
93
146
  ### DynoQuery
@@ -110,16 +163,24 @@ A model-based abstraction for a specific data type.
110
163
 
111
164
  ### Partition
112
165
  A way to manage models and data within a specific partition.
166
+ - `getPkValue()`: Returns the generated partition key value.
113
167
  - `get(sk)`: Fetches data for a specific sort key (returns a Promise).
114
168
  - `getAll()`: Fetches all items in the partition and caches them. Returns the items.
115
169
  - `create(sk, data)`: Creates an item in the partition and returns its `Model`.
116
170
  - `model(sk)`: Get a `Model` instance for a specific sort key.
171
+ - `batchGetInput(...sks)`: Generates items for batch query.
172
+ - `batchWriteInput(...items)`: Generates items for batch write.
173
+ - `batchDeleteInput(...sks)`: Generates items for batch delete.
117
174
  - `deleteAll()`: Deletes all items in the partition.
118
175
 
119
176
  ### IndexQuery
120
177
  A way to query Global Secondary Indexes.
178
+ - `getPkValue()`: Returns the generated partition key value for this index.
121
179
  - `get(skValue | options)`: Query items in the index. Supports `skValue` (string) for `begins_with` search, or an options object with `skValue`, `limit`, and `scanIndexForward`.
122
180
  - `getAll()`: Fetches all items in the index for the given partition key.
181
+ - `batchGetInput(...sks)`: Generates items for batch query.
182
+ - `batchWriteInput(...items)`: Generates items for batch write.
183
+ - `batchDeleteInput(...sks)`: Generates items for batch delete.
123
184
  - Automatically identifies models in results using `__model` and provides `getPartition()` helper.
124
185
 
125
186
  ## License
@@ -21,5 +21,18 @@ export declare class IndexQuery {
21
21
  scanIndexForward?: boolean;
22
22
  }): Promise<T[]>;
23
23
  getAll<T = any>(): Promise<T[]>;
24
+ getPkValue(): string;
25
+ /**
26
+ * Generates items for batch query.
27
+ */
28
+ batchGetInput(...sks: string[]): any[];
29
+ /**
30
+ * Generates items for batch write (put).
31
+ */
32
+ batchWriteInput(...items: any[]): any[];
33
+ /**
34
+ * Generates items for batch delete.
35
+ */
36
+ batchDeleteInput(...sks: string[]): any[];
24
37
  private mapItemToModel;
25
38
  }
@@ -68,6 +68,52 @@ class IndexQuery {
68
68
  return this.get();
69
69
  });
70
70
  }
71
+ getPkValue() {
72
+ return this.pkValue;
73
+ }
74
+ /**
75
+ * Generates items for batch query.
76
+ */
77
+ batchGetInput(...sks) {
78
+ if (sks.length === 0) {
79
+ return [{
80
+ TableName: this.tableName,
81
+ Key: { [this.pkName]: this.pkValue }
82
+ }];
83
+ }
84
+ return sks.map(sk => ({
85
+ TableName: this.tableName,
86
+ Key: {
87
+ [this.pkName]: this.pkValue,
88
+ [this.skName]: sk
89
+ }
90
+ }));
91
+ }
92
+ /**
93
+ * Generates items for batch write (put).
94
+ */
95
+ batchWriteInput(...items) {
96
+ return items.map(item => ({
97
+ TableName: this.tableName,
98
+ PutRequest: {
99
+ Item: Object.assign({ [this.pkName]: this.pkValue }, item)
100
+ }
101
+ }));
102
+ }
103
+ /**
104
+ * Generates items for batch delete.
105
+ */
106
+ batchDeleteInput(...sks) {
107
+ return sks.map(sk => ({
108
+ TableName: this.tableName,
109
+ DeleteRequest: {
110
+ Key: {
111
+ [this.pkName]: this.pkValue,
112
+ [this.skName]: sk
113
+ }
114
+ }
115
+ }));
116
+ }
71
117
  mapItemToModel(item) {
72
118
  const pkName = this.db.getPkName();
73
119
  const pkValue = item[pkName];
package/dist/index.d.ts CHANGED
@@ -21,6 +21,12 @@ export interface DynoQueryConfig {
21
21
  pkPrefix?: string;
22
22
  }>;
23
23
  }
24
+ export type BatchGetInput = BatchGetCommandInput & {
25
+ Items?: any[];
26
+ };
27
+ export type BatchWriteInput = BatchWriteCommandInput & {
28
+ Items?: any[];
29
+ };
24
30
  export declare class DynoQuery {
25
31
  private client;
26
32
  private docClient;
@@ -58,11 +64,11 @@ export declare class DynoQuery {
58
64
  /**
59
65
  * Get multiple items by their primary keys.
60
66
  */
61
- batchGet(params: BatchGetCommandInput): Promise<import("@aws-sdk/lib-dynamodb").BatchGetCommandOutput>;
67
+ batchGet(params: BatchGetInput | any, ...additionalItems: any[][]): Promise<import("@aws-sdk/lib-dynamodb").BatchGetCommandOutput>;
62
68
  /**
63
69
  * Put or delete multiple items in one or more tables.
64
70
  */
65
- batchWrite(params: BatchWriteCommandInput): Promise<import("@aws-sdk/lib-dynamodb").BatchWriteCommandOutput>;
71
+ batchWrite(params: BatchWriteInput): Promise<import("@aws-sdk/lib-dynamodb").BatchWriteCommandOutput>;
66
72
  getTableName(): string | undefined;
67
73
  getPkPrefix(): string;
68
74
  getPkName(): string;
package/dist/index.js CHANGED
@@ -22,6 +22,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
22
22
  step((generator = generator.apply(thisArg, _arguments || [])).next());
23
23
  });
24
24
  };
25
+ var __rest = (this && this.__rest) || function (s, e) {
26
+ var t = {};
27
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
28
+ t[p] = s[p];
29
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
30
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
31
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
32
+ t[p[i]] = s[p[i]];
33
+ }
34
+ return t;
35
+ };
25
36
  Object.defineProperty(exports, "__esModule", { value: true });
26
37
  exports.DynoQuery = void 0;
27
38
  const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
@@ -30,35 +41,28 @@ const partition_1 = require("./partition");
30
41
  class DynoQuery {
31
42
  constructor(config = {}) {
32
43
  this.registeredPartitions = {};
33
- const clientConfig = Object.assign({}, config);
34
- // Remove properties that are not part of DynamoDBClientConfig
35
- delete clientConfig.tableName;
36
- delete clientConfig.pkPrefix;
37
- delete clientConfig.partitions;
38
- delete clientConfig.indexes;
39
- delete clientConfig.pkName;
40
- delete clientConfig.skName;
44
+ const { tableName, pkName, skName, pkPrefix, partitions, indexes } = config, clientConfig = __rest(config, ["tableName", "pkName", "skName", "pkPrefix", "partitions", "indexes"]);
41
45
  this.client = new client_dynamodb_1.DynamoDBClient(clientConfig);
42
46
  this.docClient = lib_dynamodb_1.DynamoDBDocumentClient.from(this.client, {
43
47
  marshallOptions: {
44
48
  removeUndefinedValues: true,
45
49
  }
46
50
  });
47
- this.defaultTableName = config.tableName;
48
- this.globalPkPrefix = config.pkPrefix || "";
49
- this.pkName = config.pkName || "PK";
50
- this.skName = config.skName || "SK";
51
- if (config.partitions) {
52
- this.registeredPartitions = config.partitions;
53
- Object.entries(config.partitions).forEach(([name, def]) => {
51
+ this.defaultTableName = tableName;
52
+ this.globalPkPrefix = pkPrefix || "";
53
+ this.pkName = pkName || "PK";
54
+ this.skName = skName || "SK";
55
+ if (partitions) {
56
+ this.registeredPartitions = partitions;
57
+ Object.entries(partitions).forEach(([name, def]) => {
54
58
  this[name] = (id) => {
55
59
  return new partition_1.Partition(this, { pkPrefix: this.globalPkPrefix + def.pkPrefix }, id);
56
60
  };
57
61
  });
58
62
  }
59
- if (config.indexes) {
63
+ if (indexes) {
60
64
  const { IndexQuery } = require("./index-query");
61
- Object.entries(config.indexes).forEach(([name, def]) => {
65
+ Object.entries(indexes).forEach(([name, def]) => {
62
66
  this[name] = (id) => {
63
67
  return new IndexQuery(this, {
64
68
  indexName: def.indexName,
@@ -146,16 +150,46 @@ class DynoQuery {
146
150
  /**
147
151
  * Get multiple items by their primary keys.
148
152
  */
149
- batchGet(params) {
153
+ batchGet(params, ...additionalItems) {
150
154
  return __awaiter(this, void 0, void 0, function* () {
151
- if (this.defaultTableName && params.RequestItems) {
152
- // Note: Batch operations are a bit trickier because TableName is a key in RequestItems
153
- // This wrapper doesn't automatically add it to RequestItems yet,
154
- // but let's see if we should handle it.
155
- // For now, let's keep it as is or add it if RequestItems is empty?
156
- // Usually BatchGetCommandInput is complex.
155
+ let finalParams;
156
+ if (params && !params.RequestItems && !params.Items && (Array.isArray(params) || additionalItems.length > 0)) {
157
+ // Handle the case where arguments are multiple arrays of items
158
+ const allItems = Array.isArray(params) ? [...params] : [];
159
+ additionalItems.forEach(chunk => {
160
+ if (Array.isArray(chunk)) {
161
+ allItems.push(...chunk);
162
+ }
163
+ else {
164
+ allItems.push(chunk);
165
+ }
166
+ });
167
+ finalParams = {
168
+ Items: allItems
169
+ };
170
+ }
171
+ else {
172
+ finalParams = params;
157
173
  }
158
- const command = new lib_dynamodb_1.BatchGetCommand(params);
174
+ if (!finalParams.RequestItems && finalParams.Items) {
175
+ finalParams.RequestItems = {};
176
+ finalParams.Items.forEach((item) => {
177
+ const tableName = item.TableName || this.defaultTableName;
178
+ if (!tableName) {
179
+ throw new Error("TableName must be provided for batch operations if no default tableName is set");
180
+ }
181
+ if (!finalParams.RequestItems[tableName]) {
182
+ finalParams.RequestItems[tableName] = { Keys: [] };
183
+ }
184
+ const key = item.Key || item;
185
+ finalParams.RequestItems[tableName].Keys.push(key);
186
+ });
187
+ delete finalParams.Items;
188
+ }
189
+ else if (!finalParams.RequestItems && this.defaultTableName) {
190
+ finalParams.RequestItems = {};
191
+ }
192
+ const command = new lib_dynamodb_1.BatchGetCommand(finalParams);
159
193
  return yield this.docClient.send(command);
160
194
  });
161
195
  }
@@ -164,6 +198,24 @@ class DynoQuery {
164
198
  */
165
199
  batchWrite(params) {
166
200
  return __awaiter(this, void 0, void 0, function* () {
201
+ if (!params.RequestItems && params.Items) {
202
+ params.RequestItems = {};
203
+ params.Items.forEach((item) => {
204
+ const tableName = item.TableName || this.defaultTableName;
205
+ if (!tableName) {
206
+ throw new Error("TableName must be provided for batch operations if no default tableName is set");
207
+ }
208
+ if (!params.RequestItems[tableName]) {
209
+ params.RequestItems[tableName] = [];
210
+ }
211
+ const request = item.PutRequest || item.DeleteRequest ? item : { PutRequest: { Item: item } };
212
+ params.RequestItems[tableName].push(request);
213
+ });
214
+ delete params.Items;
215
+ }
216
+ else if (!params.RequestItems && this.defaultTableName) {
217
+ params.RequestItems = {};
218
+ }
167
219
  const command = new lib_dynamodb_1.BatchWriteCommand(params);
168
220
  return yield this.docClient.send(command);
169
221
  });
@@ -8,7 +8,7 @@ export interface PartitionConfig {
8
8
  export declare class Partition {
9
9
  protected db: DynoQuery;
10
10
  protected tableName?: string;
11
- protected pk: string;
11
+ protected pkValue: string;
12
12
  protected pkName: string;
13
13
  protected skName: string;
14
14
  protected cache: Record<string, any>;
@@ -23,7 +23,23 @@ export declare class Partition {
23
23
  * Get a model instance for a specific SK within this partition.
24
24
  */
25
25
  model<T = any>(sk: string): Model<T>;
26
- getPK(): string;
26
+ getPkValue(): string;
27
+ /**
28
+ * Generates items for batch query.
29
+ * If no SKs are provided, it might not be very useful for batchGet (which requires full keys),
30
+ * but the requirement says "will get all by pkValue" if no sk defined.
31
+ * Actually, BatchGetItem requires both PK and SK if the table has both.
32
+ * If it's for IndexQuery, it might be different.
33
+ */
34
+ batchGetInput(...sks: string[]): any[];
35
+ /**
36
+ * Generates items for batch write (put).
37
+ */
38
+ batchWriteInput(...items: any[]): any[];
39
+ /**
40
+ * Generates items for batch delete.
41
+ */
42
+ batchDeleteInput(...sks: string[]): any[];
27
43
  /**
28
44
  * Create an item in this partition and return the model.
29
45
  */
package/dist/partition.js CHANGED
@@ -20,7 +20,7 @@ class Partition {
20
20
  this.pkName = db.getPkName();
21
21
  this.skName = db.getSkName();
22
22
  if (config.pk) {
23
- this.pk = config.pk;
23
+ this.pkValue = config.pk;
24
24
  }
25
25
  else {
26
26
  const globalPrefix = db.getPkPrefix();
@@ -41,10 +41,10 @@ class Partition {
41
41
  if (globalPrefix && !partitionPrefix.startsWith(globalPrefix)) {
42
42
  finalPrefix = globalPrefix + partitionPrefix;
43
43
  }
44
- this.pk = `${finalPrefix}${id || ""}`;
44
+ this.pkValue = `${finalPrefix}${id || ""}`;
45
45
  }
46
46
  else {
47
- throw new Error("Either pk or pkPrefix must be provided in PartitionConfig");
47
+ throw new Error("Either pkValue or pkPrefix must be provided in PartitionConfig");
48
48
  }
49
49
  }
50
50
  if (!this.tableName) {
@@ -64,7 +64,7 @@ class Partition {
64
64
  "#pk": this.pkName,
65
65
  },
66
66
  ExpressionAttributeValues: {
67
- ":pk": this.pk,
67
+ ":pk": this.pkValue,
68
68
  },
69
69
  });
70
70
  const items = (response.Items || []);
@@ -83,7 +83,7 @@ class Partition {
83
83
  model(sk) {
84
84
  const config = {
85
85
  tableName: this.tableName,
86
- pkPrefix: this.pk, // In this context, pk is fixed, so prefix is the full PK
86
+ pkPrefix: this.pkValue, // In this context, pkValue is fixed, so prefix is the full PK
87
87
  skValue: sk,
88
88
  onUpdate: (updatedSk, data) => {
89
89
  if (data === null) {
@@ -96,8 +96,55 @@ class Partition {
96
96
  };
97
97
  return new model_1.Model(this.db, config);
98
98
  }
99
- getPK() {
100
- return this.pk;
99
+ getPkValue() {
100
+ return this.pkValue;
101
+ }
102
+ /**
103
+ * Generates items for batch query.
104
+ * If no SKs are provided, it might not be very useful for batchGet (which requires full keys),
105
+ * but the requirement says "will get all by pkValue" if no sk defined.
106
+ * Actually, BatchGetItem requires both PK and SK if the table has both.
107
+ * If it's for IndexQuery, it might be different.
108
+ */
109
+ batchGetInput(...sks) {
110
+ if (sks.length === 0) {
111
+ return [{
112
+ TableName: this.tableName,
113
+ Key: { [this.pkName]: this.pkValue }
114
+ }];
115
+ }
116
+ return sks.map(sk => ({
117
+ TableName: this.tableName,
118
+ Key: {
119
+ [this.pkName]: this.pkValue,
120
+ [this.skName]: sk
121
+ }
122
+ }));
123
+ }
124
+ /**
125
+ * Generates items for batch write (put).
126
+ */
127
+ batchWriteInput(...items) {
128
+ return items.map(item => ({
129
+ TableName: this.tableName,
130
+ PutRequest: {
131
+ Item: Object.assign({ [this.pkName]: this.pkValue }, item)
132
+ }
133
+ }));
134
+ }
135
+ /**
136
+ * Generates items for batch delete.
137
+ */
138
+ batchDeleteInput(...sks) {
139
+ return sks.map(sk => ({
140
+ TableName: this.tableName,
141
+ DeleteRequest: {
142
+ Key: {
143
+ [this.pkName]: this.pkValue,
144
+ [this.skName]: sk
145
+ }
146
+ }
147
+ }));
101
148
  }
102
149
  /**
103
150
  * Create an item in this partition and return the model.
@@ -142,7 +189,7 @@ class Partition {
142
189
  "#pk": this.pkName,
143
190
  },
144
191
  ExpressionAttributeValues: {
145
- ":pk": this.pk,
192
+ ":pk": this.pkValue,
146
193
  },
147
194
  });
148
195
  if (response.Items && response.Items.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynoquery",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/devspikejs/dynoquery.git"