dynoquery 0.1.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 ADDED
@@ -0,0 +1,100 @@
1
+ # DynoQuery
2
+
3
+ A lightweight wrapper for Amazon DynamoDB using the AWS SDK v3.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install dynoquery
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - Basic CRUD operations (create, get, update, delete)
14
+ - Model-based approach for easy data management
15
+ - Query and Scan support
16
+ - Batch operations (batchGet, batchWrite)
17
+ - TypeScript support
18
+
19
+ ## Usage
20
+
21
+ ```typescript
22
+ import { DynoQuery } from 'dynoquery';
23
+
24
+ const db = new DynoQuery({
25
+ region: 'us-east-1',
26
+ tableName: 'MyTable', // Define default table for single-table structure
27
+ pkPrefix: 'TENANT#A#', // Optional: Global prefix for all partitions (useful for multitenancy)
28
+ // optional endpoint for local development
29
+ // endpoint: 'http://localhost:8000'
30
+ partitions: {
31
+ User: { pkPrefix: 'USER#' },
32
+ }
33
+ });
34
+
35
+ async function example() {
36
+ // Use registered partition
37
+ // Resulting PK: TENANT#A#USER#john@example.com
38
+ const john = db.User('john@example.com');
39
+
40
+ // Load all data for this partition (optional, but good for multiple reads)
41
+ await john.loadAll();
42
+
43
+ // john.get() loads data immediately (using cache if loaded)
44
+ const userMetadata = await john.get('METADATA');
45
+ console.log(userMetadata);
46
+
47
+ // Create an item through partition
48
+ const profileModel = await john.create('PROFILE', { name: 'John Doe', email: 'john@example.com' });
49
+
50
+ // Update the model (updates both DB and partition cache)
51
+ await profileModel.update({ theme: 'dark' });
52
+
53
+ // If you need the Model instance for save/delete without immediate create:
54
+ const metaModel = john.model('METADATA');
55
+ await metaModel.save({ lastLogin: new Date().toISOString() });
56
+
57
+ // Advanced Partition usage (Subclassing)
58
+ class UserPartition extends Partition {
59
+ constructor(db: DynoQuery, email: string) {
60
+ super(db, { pkPrefix: 'USER#' }, email);
61
+ }
62
+ }
63
+
64
+ const user2 = new UserPartition(db, 'jane@example.com');
65
+ const data2 = await user2.get('METADATA');
66
+ console.log(data2);
67
+ }
68
+ ```
69
+
70
+ ## API Reference
71
+
72
+ ### DynoQuery
73
+ The main client for interacting with DynamoDB.
74
+ - `create(params)`: Put an item.
75
+ - `get(params)`: Get an item.
76
+ - `update(params)`: Update an item.
77
+ - `delete(params)`: Delete an item.
78
+ - `query(params)`: Query items.
79
+ - `scan(params)`: Scan items.
80
+ - `batchGet(params)`: Batch get items.
81
+ - `batchWrite(params)`: Batch write items.
82
+
83
+ ### Model
84
+ A model-based abstraction for a specific data type.
85
+ - `find(id?)`: Find an item by ID (PK suffix).
86
+ - `save(data, id?)`: Save an item.
87
+ - `update(data, id?)`: Update an existing item (partial update).
88
+ - `remove(id?)`: Delete an item.
89
+
90
+ ### Partition
91
+ A way to manage models and data within a specific partition.
92
+ - `get(sk)`: Fetches data for a specific sort key (returns a Promise).
93
+ - `loadAll()`: Fetches all items in the partition and caches them.
94
+ - `create(sk, data)`: Creates an item in the partition and returns its `Model`.
95
+ - `model(sk)`: Get a `Model` instance for a specific sort key.
96
+ - `deleteAll()`: Deletes all items in the partition.
97
+
98
+ ## License
99
+
100
+ MIT
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const index_1 = require("../index");
13
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
14
+ // Mock the AWS SDK
15
+ jest.mock('@aws-sdk/client-dynamodb');
16
+ jest.mock('@aws-sdk/lib-dynamodb', () => {
17
+ const actual = jest.requireActual('@aws-sdk/lib-dynamodb');
18
+ return Object.assign(Object.assign({}, actual), { DynamoDBDocumentClient: {
19
+ from: jest.fn().mockReturnValue({
20
+ send: jest.fn(),
21
+ }),
22
+ } });
23
+ });
24
+ describe('DynoQuery', () => {
25
+ let dynoQuery;
26
+ let mockDocClient;
27
+ beforeEach(() => {
28
+ dynoQuery = new index_1.DynoQuery({ region: 'us-east-1' });
29
+ mockDocClient = lib_dynamodb_1.DynamoDBDocumentClient.from.mock.results[0].value;
30
+ });
31
+ afterEach(() => {
32
+ jest.clearAllMocks();
33
+ });
34
+ test('create should call PutCommand', () => __awaiter(void 0, void 0, void 0, function* () {
35
+ const params = { TableName: 'TestTable', Item: { id: '1', name: 'Test' } };
36
+ yield dynoQuery.create(params);
37
+ expect(mockDocClient.send).toHaveBeenCalled();
38
+ }));
39
+ test('get should call GetCommand', () => __awaiter(void 0, void 0, void 0, function* () {
40
+ const params = { TableName: 'TestTable', Key: { id: '1' } };
41
+ yield dynoQuery.get(params);
42
+ expect(mockDocClient.send).toHaveBeenCalled();
43
+ }));
44
+ test('update should call UpdateCommand', () => __awaiter(void 0, void 0, void 0, function* () {
45
+ const params = {
46
+ TableName: 'TestTable',
47
+ Key: { id: '1' },
48
+ UpdateExpression: 'set #n = :n',
49
+ ExpressionAttributeNames: { '#n': 'name' },
50
+ ExpressionAttributeValues: { ':n': 'Updated' },
51
+ };
52
+ yield dynoQuery.update(params);
53
+ expect(mockDocClient.send).toHaveBeenCalled();
54
+ }));
55
+ test('delete should call DeleteCommand', () => __awaiter(void 0, void 0, void 0, function* () {
56
+ const params = { TableName: 'TestTable', Key: { id: '1' } };
57
+ yield dynoQuery.delete(params);
58
+ expect(mockDocClient.send).toHaveBeenCalled();
59
+ }));
60
+ test('query should call QueryCommand', () => __awaiter(void 0, void 0, void 0, function* () {
61
+ const params = {
62
+ TableName: 'TestTable',
63
+ KeyConditionExpression: 'id = :id',
64
+ ExpressionAttributeValues: { ':id': '1' },
65
+ };
66
+ yield dynoQuery.query(params);
67
+ expect(mockDocClient.send).toHaveBeenCalled();
68
+ }));
69
+ test('scan should call ScanCommand', () => __awaiter(void 0, void 0, void 0, function* () {
70
+ const params = { TableName: 'TestTable' };
71
+ yield dynoQuery.scan(params);
72
+ expect(mockDocClient.send).toHaveBeenCalled();
73
+ }));
74
+ test('batchGet should call BatchGetCommand', () => __awaiter(void 0, void 0, void 0, function* () {
75
+ const params = {
76
+ RequestItems: {
77
+ TestTable: {
78
+ Keys: [{ id: '1' }, { id: '2' }],
79
+ },
80
+ },
81
+ };
82
+ yield dynoQuery.batchGet(params);
83
+ expect(mockDocClient.send).toHaveBeenCalled();
84
+ }));
85
+ test('batchWrite should call BatchWriteCommand', () => __awaiter(void 0, void 0, void 0, function* () {
86
+ const params = {
87
+ RequestItems: {
88
+ TestTable: [
89
+ { PutRequest: { Item: { id: '1', name: 'Test1' } } },
90
+ { PutRequest: { Item: { id: '2', name: 'Test2' } } },
91
+ ],
92
+ },
93
+ };
94
+ yield dynoQuery.batchWrite(params);
95
+ expect(mockDocClient.send).toHaveBeenCalled();
96
+ }));
97
+ });
@@ -0,0 +1,59 @@
1
+ import { PutCommandInput, GetCommandInput, UpdateCommandInput, DeleteCommandInput, QueryCommandInput, ScanCommandInput, BatchGetCommandInput, BatchWriteCommandInput } from "@aws-sdk/lib-dynamodb";
2
+ export interface DynoQueryConfig {
3
+ tableName?: string;
4
+ region?: string;
5
+ endpoint?: string;
6
+ pkPrefix?: string;
7
+ credentials?: {
8
+ accessKeyId: string;
9
+ secretAccessKey: string;
10
+ sessionToken?: string;
11
+ };
12
+ partitions?: Record<string, {
13
+ pkPrefix: string;
14
+ }>;
15
+ }
16
+ export declare class DynoQuery {
17
+ private client;
18
+ private docClient;
19
+ private defaultTableName?;
20
+ private globalPkPrefix;
21
+ [key: string]: any;
22
+ constructor(config?: DynoQueryConfig);
23
+ /**
24
+ * Create or replace an item in the table.
25
+ */
26
+ create(params: PutCommandInput): Promise<import("@aws-sdk/lib-dynamodb").PutCommandOutput>;
27
+ /**
28
+ * Get an item by its primary key.
29
+ */
30
+ get(params: GetCommandInput): Promise<import("@aws-sdk/lib-dynamodb").GetCommandOutput>;
31
+ /**
32
+ * Update an existing item.
33
+ */
34
+ update(params: UpdateCommandInput): Promise<import("@aws-sdk/lib-dynamodb").UpdateCommandOutput>;
35
+ /**
36
+ * Delete an item by its primary key.
37
+ */
38
+ delete(params: DeleteCommandInput): Promise<import("@aws-sdk/lib-dynamodb").DeleteCommandOutput>;
39
+ /**
40
+ * Query items based on primary key and sort key conditions.
41
+ */
42
+ query(params: QueryCommandInput): Promise<import("@aws-sdk/lib-dynamodb").QueryCommandOutput>;
43
+ /**
44
+ * Scan the table or index for items.
45
+ */
46
+ scan(params: ScanCommandInput): Promise<import("@aws-sdk/lib-dynamodb").ScanCommandOutput>;
47
+ /**
48
+ * Get multiple items by their primary keys.
49
+ */
50
+ batchGet(params: BatchGetCommandInput): Promise<import("@aws-sdk/lib-dynamodb").BatchGetCommandOutput>;
51
+ /**
52
+ * Put or delete multiple items in one or more tables.
53
+ */
54
+ batchWrite(params: BatchWriteCommandInput): Promise<import("@aws-sdk/lib-dynamodb").BatchWriteCommandOutput>;
55
+ getTableName(): string | undefined;
56
+ getPkPrefix(): string;
57
+ }
58
+ export * from "./model";
59
+ export * from "./partition";
package/dist/index.js ADDED
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
17
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
18
+ return new (P || (P = Promise))(function (resolve, reject) {
19
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
20
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
21
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
22
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
23
+ });
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.DynoQuery = void 0;
27
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
28
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
29
+ const partition_1 = require("./partition");
30
+ class DynoQuery {
31
+ constructor(config = {}) {
32
+ const clientConfig = Object.assign({}, config);
33
+ // Remove properties that are not part of DynamoDBClientConfig
34
+ delete clientConfig.tableName;
35
+ delete clientConfig.pkPrefix;
36
+ delete clientConfig.partitions;
37
+ this.client = new client_dynamodb_1.DynamoDBClient(clientConfig);
38
+ this.docClient = lib_dynamodb_1.DynamoDBDocumentClient.from(this.client, {
39
+ marshallOptions: {
40
+ removeUndefinedValues: true,
41
+ }
42
+ });
43
+ this.defaultTableName = config.tableName;
44
+ this.globalPkPrefix = config.pkPrefix || "";
45
+ if (config.partitions) {
46
+ Object.entries(config.partitions).forEach(([name, def]) => {
47
+ this[name] = (id) => {
48
+ return new partition_1.Partition(this, { pkPrefix: this.globalPkPrefix + def.pkPrefix }, id);
49
+ };
50
+ });
51
+ }
52
+ }
53
+ /**
54
+ * Create or replace an item in the table.
55
+ */
56
+ create(params) {
57
+ return __awaiter(this, void 0, void 0, function* () {
58
+ if (!params.TableName && this.defaultTableName) {
59
+ params.TableName = this.defaultTableName;
60
+ }
61
+ const command = new lib_dynamodb_1.PutCommand(params);
62
+ return yield this.docClient.send(command);
63
+ });
64
+ }
65
+ /**
66
+ * Get an item by its primary key.
67
+ */
68
+ get(params) {
69
+ return __awaiter(this, void 0, void 0, function* () {
70
+ if (!params.TableName && this.defaultTableName) {
71
+ params.TableName = this.defaultTableName;
72
+ }
73
+ const command = new lib_dynamodb_1.GetCommand(params);
74
+ return yield this.docClient.send(command);
75
+ });
76
+ }
77
+ /**
78
+ * Update an existing item.
79
+ */
80
+ update(params) {
81
+ return __awaiter(this, void 0, void 0, function* () {
82
+ if (!params.TableName && this.defaultTableName) {
83
+ params.TableName = this.defaultTableName;
84
+ }
85
+ const command = new lib_dynamodb_1.UpdateCommand(params);
86
+ return yield this.docClient.send(command);
87
+ });
88
+ }
89
+ /**
90
+ * Delete an item by its primary key.
91
+ */
92
+ delete(params) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ if (!params.TableName && this.defaultTableName) {
95
+ params.TableName = this.defaultTableName;
96
+ }
97
+ const command = new lib_dynamodb_1.DeleteCommand(params);
98
+ return yield this.docClient.send(command);
99
+ });
100
+ }
101
+ /**
102
+ * Query items based on primary key and sort key conditions.
103
+ */
104
+ query(params) {
105
+ return __awaiter(this, void 0, void 0, function* () {
106
+ if (!params.TableName && this.defaultTableName) {
107
+ params.TableName = this.defaultTableName;
108
+ }
109
+ const command = new lib_dynamodb_1.QueryCommand(params);
110
+ return yield this.docClient.send(command);
111
+ });
112
+ }
113
+ /**
114
+ * Scan the table or index for items.
115
+ */
116
+ scan(params) {
117
+ return __awaiter(this, void 0, void 0, function* () {
118
+ if (!params.TableName && this.defaultTableName) {
119
+ params.TableName = this.defaultTableName;
120
+ }
121
+ const command = new lib_dynamodb_1.ScanCommand(params);
122
+ return yield this.docClient.send(command);
123
+ });
124
+ }
125
+ /**
126
+ * Get multiple items by their primary keys.
127
+ */
128
+ batchGet(params) {
129
+ return __awaiter(this, void 0, void 0, function* () {
130
+ if (this.defaultTableName && params.RequestItems) {
131
+ // Note: Batch operations are a bit trickier because TableName is a key in RequestItems
132
+ // This wrapper doesn't automatically add it to RequestItems yet,
133
+ // but let's see if we should handle it.
134
+ // For now, let's keep it as is or add it if RequestItems is empty?
135
+ // Usually BatchGetCommandInput is complex.
136
+ }
137
+ const command = new lib_dynamodb_1.BatchGetCommand(params);
138
+ return yield this.docClient.send(command);
139
+ });
140
+ }
141
+ /**
142
+ * Put or delete multiple items in one or more tables.
143
+ */
144
+ batchWrite(params) {
145
+ return __awaiter(this, void 0, void 0, function* () {
146
+ const command = new lib_dynamodb_1.BatchWriteCommand(params);
147
+ return yield this.docClient.send(command);
148
+ });
149
+ }
150
+ getTableName() {
151
+ return this.defaultTableName;
152
+ }
153
+ getPkPrefix() {
154
+ return this.globalPkPrefix;
155
+ }
156
+ }
157
+ exports.DynoQuery = DynoQuery;
158
+ __exportStar(require("./model"), exports);
159
+ __exportStar(require("./partition"), exports);
@@ -0,0 +1,34 @@
1
+ import { DynoQuery } from "./index";
2
+ export interface ModelConfig<T = any> {
3
+ tableName?: string;
4
+ pkPrefix: string;
5
+ skValue: string;
6
+ onUpdate?: (sk: any, data: any) => void;
7
+ }
8
+ export declare class Model<T = any> {
9
+ protected db: DynoQuery;
10
+ protected tableName?: string;
11
+ protected pkPrefix: string;
12
+ protected skValue: string;
13
+ protected onUpdate?: (sk: any, data: any) => void;
14
+ constructor(db: DynoQuery, config: ModelConfig<T>);
15
+ protected getTableName(): string;
16
+ getPK(id?: string): string;
17
+ /**
18
+ * Find an item by its identifier (part of the PK).
19
+ * Assumes the SK is fixed as defined in ModelConfig.
20
+ */
21
+ find(id?: string): Promise<T | null>;
22
+ /**
23
+ * Save an item.
24
+ */
25
+ save(data: T, id?: string): Promise<void>;
26
+ /**
27
+ * Update an existing item.
28
+ */
29
+ update(data: Partial<T>, id?: string): Promise<void>;
30
+ /**
31
+ * Delete an item by its identifier.
32
+ */
33
+ remove(id?: string): Promise<void>;
34
+ }
package/dist/model.js ADDED
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.Model = void 0;
13
+ class Model {
14
+ constructor(db, config) {
15
+ this.db = db;
16
+ this.tableName = config.tableName || db.getTableName();
17
+ this.pkPrefix = config.pkPrefix;
18
+ this.skValue = config.skValue;
19
+ this.onUpdate = config.onUpdate;
20
+ if (!this.tableName) {
21
+ throw new Error("TableName must be provided in ModelConfig or DynoQueryConfig");
22
+ }
23
+ }
24
+ getTableName() {
25
+ return this.tableName;
26
+ }
27
+ getPK(id = "") {
28
+ return `${this.pkPrefix}${id}`;
29
+ }
30
+ /**
31
+ * Find an item by its identifier (part of the PK).
32
+ * Assumes the SK is fixed as defined in ModelConfig.
33
+ */
34
+ find() {
35
+ return __awaiter(this, arguments, void 0, function* (id = "") {
36
+ const response = yield this.db.get({
37
+ TableName: this.getTableName(),
38
+ Key: {
39
+ PK: this.getPK(id),
40
+ SK: this.skValue,
41
+ },
42
+ });
43
+ return response.Item || null;
44
+ });
45
+ }
46
+ /**
47
+ * Save an item.
48
+ */
49
+ save(data_1) {
50
+ return __awaiter(this, arguments, void 0, function* (data, id = "") {
51
+ const item = Object.assign({ PK: this.getPK(id), SK: this.skValue }, data);
52
+ yield this.db.create({
53
+ TableName: this.getTableName(),
54
+ Item: item,
55
+ });
56
+ if (this.onUpdate) {
57
+ this.onUpdate(this.skValue, item);
58
+ }
59
+ });
60
+ }
61
+ /**
62
+ * Update an existing item.
63
+ */
64
+ update(data_1) {
65
+ return __awaiter(this, arguments, void 0, function* (data, id = "") {
66
+ // For simplicity in this wrapper, we use create (PutItem) to update the whole item
67
+ // but the user might expect a partial update if we use UpdateCommand.
68
+ // However, for single table and this kind of wrapper, often we just put the whole item.
69
+ // If we want to support partial updates in the cache, we need the current item.
70
+ const response = yield this.db.get({
71
+ TableName: this.getTableName(),
72
+ Key: {
73
+ PK: this.getPK(id),
74
+ SK: this.skValue,
75
+ },
76
+ });
77
+ const current = response.Item || {};
78
+ const updated = Object.assign(Object.assign({}, current), data);
79
+ yield this.save(updated, id);
80
+ });
81
+ }
82
+ /**
83
+ * Delete an item by its identifier.
84
+ */
85
+ remove() {
86
+ return __awaiter(this, arguments, void 0, function* (id = "") {
87
+ yield this.db.delete({
88
+ TableName: this.getTableName(),
89
+ Key: {
90
+ PK: this.getPK(id),
91
+ SK: this.skValue,
92
+ },
93
+ });
94
+ if (this.onUpdate) {
95
+ this.onUpdate(this.skValue, null);
96
+ }
97
+ });
98
+ }
99
+ }
100
+ exports.Model = Model;
@@ -0,0 +1,38 @@
1
+ import { DynoQuery } from "./index";
2
+ import { Model } from "./model";
3
+ export interface PartitionConfig {
4
+ tableName?: string;
5
+ pk?: string;
6
+ pkPrefix?: string;
7
+ }
8
+ export declare class Partition {
9
+ protected db: DynoQuery;
10
+ protected tableName?: string;
11
+ protected pk: string;
12
+ protected cache: Record<string, any>;
13
+ protected isLoaded: boolean;
14
+ constructor(db: DynoQuery, config: PartitionConfig, id?: string);
15
+ /**
16
+ * Load all data for this partition key.
17
+ */
18
+ loadAll(): Promise<this>;
19
+ /**
20
+ * Get a model instance for a specific SK within this partition.
21
+ */
22
+ model<T = any>(sk: string): Model<T>;
23
+ getPK(): string;
24
+ /**
25
+ * Create an item in this partition and return the model.
26
+ */
27
+ create<T = any>(sk: string, data: T): Promise<Model<T>>;
28
+ /**
29
+ * Get data for a specific SK within this partition.
30
+ * If the partition is loaded, it returns from cache.
31
+ * Otherwise, it fetches the data immediately.
32
+ */
33
+ get<T = any>(sk: string): Promise<T | null>;
34
+ /**
35
+ * Delete all data in this partition.
36
+ */
37
+ deleteAll(): Promise<void>;
38
+ }
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.Partition = void 0;
13
+ const model_1 = require("./model");
14
+ class Partition {
15
+ constructor(db, config, id) {
16
+ this.cache = {};
17
+ this.isLoaded = false;
18
+ this.db = db;
19
+ this.tableName = config.tableName || db.getTableName();
20
+ if (config.pk) {
21
+ this.pk = config.pk;
22
+ }
23
+ else {
24
+ const globalPrefix = db.getPkPrefix();
25
+ const partitionPrefix = config.pkPrefix || "";
26
+ if (!config.pkPrefix && !id && !globalPrefix) {
27
+ // This check might be too strict if we allow empty prefixes,
28
+ // but Partition expects some way to form a PK.
29
+ // If config.pkPrefix is missing, we use id.
30
+ }
31
+ if (config.pkPrefix || id || globalPrefix) {
32
+ // If it's a Partition registered in DynoQuery, config.pkPrefix will already
33
+ // include the global prefix because of how we register it.
34
+ // But for manual instantiation (new Partition), we should probably add the global prefix too.
35
+ let finalPrefix = partitionPrefix;
36
+ // If it's NOT already starting with globalPrefix and globalPrefix exists, prepend it.
37
+ // Actually, better to just trust the caller OR standardise.
38
+ // Let's look at how we want to handle manual instantiation.
39
+ if (globalPrefix && !partitionPrefix.startsWith(globalPrefix)) {
40
+ finalPrefix = globalPrefix + partitionPrefix;
41
+ }
42
+ this.pk = `${finalPrefix}${id || ""}`;
43
+ }
44
+ else {
45
+ throw new Error("Either pk or pkPrefix must be provided in PartitionConfig");
46
+ }
47
+ }
48
+ if (!this.tableName) {
49
+ throw new Error("TableName must be provided in PartitionConfig or DynoQueryConfig");
50
+ }
51
+ }
52
+ /**
53
+ * Load all data for this partition key.
54
+ */
55
+ loadAll() {
56
+ return __awaiter(this, void 0, void 0, function* () {
57
+ const response = yield this.db.query({
58
+ TableName: this.tableName,
59
+ KeyConditionExpression: "PK = :pk",
60
+ ExpressionAttributeValues: {
61
+ ":pk": this.pk,
62
+ },
63
+ });
64
+ if (response.Items) {
65
+ response.Items.forEach((item) => {
66
+ if (item.SK) {
67
+ this.cache[item.SK] = item;
68
+ }
69
+ });
70
+ }
71
+ this.isLoaded = true;
72
+ return this;
73
+ });
74
+ }
75
+ /**
76
+ * Get a model instance for a specific SK within this partition.
77
+ */
78
+ model(sk) {
79
+ const config = {
80
+ tableName: this.tableName,
81
+ pkPrefix: this.pk, // In this context, pk is fixed, so prefix is the full PK
82
+ skValue: sk,
83
+ onUpdate: (updatedSk, data) => {
84
+ if (data === null) {
85
+ delete this.cache[updatedSk];
86
+ }
87
+ else {
88
+ this.cache[updatedSk] = data;
89
+ }
90
+ }
91
+ };
92
+ return new model_1.Model(this.db, config);
93
+ }
94
+ getPK() {
95
+ return this.pk;
96
+ }
97
+ /**
98
+ * Create an item in this partition and return the model.
99
+ */
100
+ create(sk, data) {
101
+ return __awaiter(this, void 0, void 0, function* () {
102
+ const m = this.model(sk);
103
+ yield m.save(data);
104
+ return m;
105
+ });
106
+ }
107
+ /**
108
+ * Get data for a specific SK within this partition.
109
+ * If the partition is loaded, it returns from cache.
110
+ * Otherwise, it fetches the data immediately.
111
+ */
112
+ get(sk) {
113
+ return __awaiter(this, void 0, void 0, function* () {
114
+ if (this.cache[sk] !== undefined) {
115
+ return this.cache[sk] || null;
116
+ }
117
+ if (this.isLoaded) {
118
+ return null;
119
+ }
120
+ const model = this.model(sk);
121
+ const data = yield model.find();
122
+ if (data) {
123
+ this.cache[sk] = data;
124
+ }
125
+ return data;
126
+ });
127
+ }
128
+ /**
129
+ * Delete all data in this partition.
130
+ */
131
+ deleteAll() {
132
+ return __awaiter(this, void 0, void 0, function* () {
133
+ const response = yield this.db.query({
134
+ TableName: this.tableName,
135
+ KeyConditionExpression: "PK = :pk",
136
+ ExpressionAttributeValues: {
137
+ ":pk": this.pk,
138
+ },
139
+ });
140
+ if (response.Items && response.Items.length > 0) {
141
+ // DynamoDB BatchWriteItem supports up to 25 requests at once
142
+ const items = response.Items;
143
+ const chunks = [];
144
+ for (let i = 0; i < items.length; i += 25) {
145
+ chunks.push(items.slice(i, i + 25));
146
+ }
147
+ for (const chunk of chunks) {
148
+ const deleteRequests = chunk.map((item) => ({
149
+ DeleteRequest: {
150
+ Key: {
151
+ PK: item.PK,
152
+ SK: item.SK,
153
+ },
154
+ },
155
+ }));
156
+ yield this.db.batchWrite({
157
+ RequestItems: {
158
+ [this.tableName]: deleteRequests,
159
+ },
160
+ });
161
+ }
162
+ }
163
+ this.cache = {};
164
+ this.isLoaded = false;
165
+ });
166
+ }
167
+ }
168
+ exports.Partition = Partition;
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "dynoquery",
3
+ "version": "0.1.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/devspikejs/dynoquery.git"
7
+ },
8
+ "description": "A lightweight wrapper for Amazon DynamoDB using the AWS SDK v3",
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "test": "jest src",
18
+ "prepublishOnly": "npm run build"
19
+ },
20
+ "keywords": [
21
+ "dynamodb",
22
+ "aws",
23
+ "wrapper",
24
+ "query",
25
+ "sdk-v3"
26
+ ],
27
+ "author": "",
28
+ "license": "MIT",
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "dependencies": {
33
+ "@aws-sdk/client-dynamodb": "^3.980.0",
34
+ "@aws-sdk/lib-dynamodb": "^3.980.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/jest": "^30.0.0",
38
+ "jest": "^30.2.0",
39
+ "ts-jest": "^29.4.6",
40
+ "typescript": "^5.5.3"
41
+ }
42
+ }