betterddb 0.6.4 → 0.6.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/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "betterddb",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "A definition-based DynamoDB wrapper library that provides a schema-driven and fully typesafe DAL.",
5
- "main": "lib/index.js",
6
- "types": "lib/index.d.ts",
5
+ "types": "./lib/index.d.ts",
6
+ "main": "./lib/index.js",
7
+ "files": [
8
+ "lib/**/*"
9
+ ],
7
10
  "scripts": {
8
11
  "prepublishOnly": "npm run build",
9
12
  "docker:up": "docker-compose up -d",
package/.eslintrc.cjs DELETED
@@ -1,41 +0,0 @@
1
- /** @type {import("eslint").Linter.Config} */
2
- const config = {
3
- "parser": "@typescript-eslint/parser",
4
- "parserOptions": {
5
- "project": true
6
- },
7
- "plugins": [
8
- "@typescript-eslint"
9
- ],
10
- "extends": [
11
- "plugin:@typescript-eslint/recommended-type-checked",
12
- "plugin:@typescript-eslint/stylistic-type-checked"
13
- ],
14
- "rules": {
15
- "@typescript-eslint/array-type": "off",
16
- "@typescript-eslint/consistent-type-definitions": "off",
17
- "@typescript-eslint/consistent-type-imports": [
18
- "warn",
19
- {
20
- "prefer": "type-imports",
21
- "fixStyle": "inline-type-imports"
22
- }
23
- ],
24
- "@typescript-eslint/no-unused-vars": [
25
- "warn",
26
- {
27
- "argsIgnorePattern": "^_"
28
- }
29
- ],
30
- "@typescript-eslint/require-await": "off",
31
- "@typescript-eslint/no-misused-promises": [
32
- "error",
33
- {
34
- "checksVoidReturn": {
35
- "attributes": false
36
- }
37
- }
38
- ]
39
- }
40
- }
41
- module.exports = config;
@@ -1,33 +0,0 @@
1
- # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
- # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3
-
4
- name: Node.js Package
5
-
6
- on:
7
- release:
8
- types: [created]
9
-
10
- jobs:
11
- build:
12
- runs-on: ubuntu-latest
13
- steps:
14
- - uses: actions/checkout@v4
15
- - uses: actions/setup-node@v4
16
- with:
17
- node-version: 20
18
- - run: npm ci
19
- - run: npm test
20
-
21
- publish-npm:
22
- needs: build
23
- runs-on: ubuntu-latest
24
- steps:
25
- - uses: actions/checkout@v4
26
- - uses: actions/setup-node@v4
27
- with:
28
- node-version: 20
29
- registry-url: https://registry.npmjs.org/
30
- - run: npm ci
31
- - run: npm publish
32
- env:
33
- NODE_AUTH_TOKEN: ${{secrets.npm_token}}
package/LICENCSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Ryan Rawlings Wang
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,16 +0,0 @@
1
- version: '3.8'
2
- services:
3
- betterddb-localstack:
4
- image: localstack/localstack:latest
5
- container_name: betterddb-localstack
6
- ports:
7
- - "4566:4566" # Main edge port
8
- - "4571:4571"
9
- environment:
10
- - SERVICES=dynamodb
11
- - DEFAULT_REGION=us-east-1
12
- - DATA_DIR=/tmp/localstack_data
13
- - HOST_TMP_FOLDER=${TMPDIR:-/tmp}/localstack
14
- - LOCALSTACK_UI=1
15
- volumes:
16
- - "./localstack-tmp:/tmp/localstack_data"
package/jest.config.js DELETED
@@ -1,16 +0,0 @@
1
- const config = {
2
- testEnvironment: 'node',
3
- roots: ['<rootDir>/test/'],
4
- testMatch: ['**/*.test.ts'],
5
- transform: {
6
- '^.+\\.(ts|tsx|js|jsx)$': 'ts-jest'
7
- },
8
- moduleNameMapper: {
9
- '^@/(.*)$': '<rootDir>/src/$1',
10
- '^~/(.*)$': '<rootDir>/src/$1'
11
- },
12
- extensionsToTreatAsEsm: ['.ts', '.tsx'],
13
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
14
- }
15
-
16
- export default config;
@@ -1,6 +0,0 @@
1
- /** @type {import('prettier').Config} */
2
- const config = {
3
- tabWidth: 2,
4
- };
5
-
6
- export default config;
package/src/betterddb.ts DELETED
@@ -1,263 +0,0 @@
1
- import { z } from "zod";
2
- import { QueryBuilder } from "./builders/query-builder";
3
- import { ScanBuilder } from "./builders/scan-builder";
4
- import { UpdateBuilder } from "./builders/update-builder";
5
- import { CreateBuilder } from "./builders/create-builder";
6
- import { GetBuilder } from "./builders/get-builder";
7
- import { DeleteBuilder } from "./builders/delete-builder";
8
- import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
9
- import { BatchGetBuilder } from "./builders/batch-get-builder";
10
- export type PrimaryKeyValue = string | number;
11
-
12
- /**
13
- * A key definition can be either a simple key (a property name)
14
- * or an object containing a build function that computes the value.
15
- * (In this design, the attribute name is provided separately.)
16
- */
17
- export type KeyDefinition<T> =
18
- | keyof T
19
- | {
20
- build: (rawKey: Partial<T>) => string;
21
- };
22
-
23
- /**
24
- * Configuration for a primary (partition) key.
25
- */
26
- export interface PrimaryKeyConfig<T> {
27
- /** The attribute name for the primary key in DynamoDB */
28
- name: string;
29
- /** How to compute the key value; if a keyof T, then the raw value is used;
30
- * if an object, the build function is used.
31
- */
32
- definition: KeyDefinition<T>;
33
- }
34
-
35
- /**
36
- * Configuration for a sort key.
37
- */
38
- export interface SortKeyConfig<T> {
39
- /** The attribute name for the sort key in DynamoDB */
40
- name: string;
41
- /** How to compute the sort key value */
42
- definition: KeyDefinition<T>;
43
- }
44
-
45
- /**
46
- * Configuration for a Global Secondary Index (GSI).
47
- */
48
- export interface GSIConfig<T> {
49
- /** The name of the GSI in DynamoDB */
50
- name: string;
51
- /** The primary key configuration for the GSI */
52
- primary: PrimaryKeyConfig<T>;
53
- /** The sort key configuration for the GSI, if any */
54
- sort?: SortKeyConfig<T>;
55
- }
56
-
57
- /**
58
- * Keys configuration for the table.
59
- */
60
- export interface KeysConfig<T> {
61
- primary: PrimaryKeyConfig<T>;
62
- sort?: SortKeyConfig<T>;
63
- gsis?: {
64
- [gsiName: string]: GSIConfig<T>;
65
- };
66
- }
67
-
68
- /**
69
- * Options for initializing BetterDDB.
70
- */
71
- export interface BetterDDBOptions<T> {
72
- schema: z.ZodType<T, z.ZodTypeDef, any>;
73
- tableName: string;
74
- entityType?: string;
75
- keys: KeysConfig<T>;
76
- client: DynamoDBDocumentClient;
77
- counter?: boolean;
78
- /**
79
- * If true, automatically inject timestamp fields:
80
- * - On create, sets both `createdAt` and `updatedAt`
81
- * - On update, sets `updatedAt`
82
- *
83
- * (T should include these fields if enabled.)
84
- */
85
- timestamps?: boolean;
86
- }
87
-
88
- /**
89
- * BetterDDB is a definition-based DynamoDB wrapper library.
90
- */
91
- export class BetterDDB<T> {
92
- protected schema: z.ZodType<T, z.ZodTypeDef, any>;
93
- protected tableName: string;
94
- protected entityType?: string;
95
- protected client: DynamoDBDocumentClient;
96
- protected keys: KeysConfig<T>;
97
- protected timestamps: boolean;
98
- protected counter: boolean;
99
-
100
- constructor(options: BetterDDBOptions<T>) {
101
- this.schema = options.schema;
102
- this.tableName = options.tableName;
103
- this.entityType = options.entityType?.toUpperCase();
104
- this.keys = options.keys;
105
- this.client = options.client;
106
- this.timestamps = options.timestamps ?? false;
107
- this.counter = options.counter ?? false;
108
- }
109
-
110
- public getCounter(): boolean {
111
- return this.counter;
112
- }
113
-
114
- public getKeys(): KeysConfig<T> {
115
- return this.keys;
116
- }
117
-
118
- public getTableName(): string {
119
- return this.tableName;
120
- }
121
-
122
- public getClient(): DynamoDBDocumentClient {
123
- return this.client;
124
- }
125
-
126
- public getSchema(): z.ZodType<T, z.ZodTypeDef, any> {
127
- return this.schema;
128
- }
129
-
130
- public getTimestamps(): boolean {
131
- return this.timestamps;
132
- }
133
-
134
- public getEntityType(): string | undefined {
135
- return this.entityType;
136
- }
137
-
138
- // Helper: Retrieve the key value from a KeyDefinition.
139
- protected getKeyValue(def: KeyDefinition<T>, rawKey: Partial<T>): string {
140
- if (
141
- typeof def === "string" ||
142
- typeof def === "number" ||
143
- typeof def === "symbol"
144
- ) {
145
- return String(rawKey[def]);
146
- } else {
147
- return def.build(rawKey);
148
- }
149
- }
150
-
151
- /**
152
- * Build the primary key from a raw key object.
153
- */
154
- public buildKey(rawKey: Partial<T>): Record<string, any> {
155
- const keyObj: Record<string, any> = {};
156
-
157
- // For primary (partition) key:
158
- const pkConfig = this.keys.primary;
159
- keyObj[pkConfig.name] =
160
- typeof pkConfig.definition === "string" ||
161
- typeof pkConfig.definition === "number" ||
162
- typeof pkConfig.definition === "symbol"
163
- ? String((rawKey as any)[pkConfig.definition])
164
- : pkConfig.definition.build(rawKey);
165
-
166
- // For sort key, if defined:
167
- if (this.keys.sort) {
168
- const skConfig = this.keys.sort;
169
- keyObj[skConfig.name] =
170
- typeof skConfig.definition === "string" ||
171
- typeof skConfig.definition === "number" ||
172
- typeof skConfig.definition === "symbol"
173
- ? String((rawKey as any)[skConfig.definition])
174
- : skConfig.definition.build(rawKey);
175
- }
176
- return keyObj;
177
- }
178
-
179
- /**
180
- * Build index attributes for each defined GSI.
181
- */
182
- public buildIndexes(rawItem: Partial<T>): Record<string, any> {
183
- const indexAttributes: Record<string, any> = {};
184
- if (this.keys.gsis) {
185
- for (const gsiName in this.keys.gsis) {
186
- const gsiConfig = this.keys.gsis[gsiName];
187
-
188
- // Compute primary index attribute.
189
- const primaryConfig = gsiConfig!.primary;
190
- indexAttributes[primaryConfig.name] =
191
- typeof primaryConfig.definition === "string" ||
192
- typeof primaryConfig.definition === "number" ||
193
- typeof primaryConfig.definition === "symbol"
194
- ? String((rawItem as any)[primaryConfig.definition])
195
- : primaryConfig.definition.build(rawItem);
196
-
197
- // Compute sort index attribute if provided.
198
- if (gsiConfig?.sort) {
199
- const sortConfig = gsiConfig.sort;
200
- indexAttributes[sortConfig.name] =
201
- typeof sortConfig.definition === "string" ||
202
- typeof sortConfig.definition === "number" ||
203
- typeof sortConfig.definition === "symbol"
204
- ? String((rawItem as any)[sortConfig.definition])
205
- : sortConfig.definition.build(rawItem);
206
- }
207
- }
208
- }
209
- return indexAttributes;
210
- }
211
-
212
- /**
213
- * Create an item:
214
- * - Computes primary key and index attributes,
215
- * - Optionally injects timestamps,
216
- * - Validates the item and writes it to DynamoDB.
217
- */
218
- public create(item: T): CreateBuilder<T> {
219
- return new CreateBuilder<T>(this, item);
220
- }
221
-
222
- /**
223
- * Get an item by its primary key.
224
- */
225
- public get(rawKey: Partial<T>): GetBuilder<T> {
226
- return new GetBuilder<T>(this, rawKey);
227
- }
228
-
229
- /**
230
- * Get multiple items by their primary keys.
231
- */
232
- public batchGet(rawKeys: Partial<T>[]): BatchGetBuilder<T> {
233
- return new BatchGetBuilder<T>(this, rawKeys);
234
- }
235
-
236
- /**
237
- * Update an item.
238
- */
239
- public update(key: Partial<T>): UpdateBuilder<T> {
240
- return new UpdateBuilder<T>(this, key);
241
- }
242
-
243
- /**
244
- * Delete an item.
245
- */
246
- public delete(rawKey: Partial<T>): DeleteBuilder<T> {
247
- return new DeleteBuilder<T>(this, rawKey);
248
- }
249
-
250
- /**
251
- * Query items.
252
- */
253
- public query(key: Partial<T>): QueryBuilder<T> {
254
- return new QueryBuilder<T>(this, key);
255
- }
256
-
257
- /**
258
- * Scan for items.
259
- */
260
- public scan(): ScanBuilder<T> {
261
- return new ScanBuilder<T>(this);
262
- }
263
- }
@@ -1,56 +0,0 @@
1
- import { BetterDDB } from "../betterddb";
2
- import { BatchGetCommand } from "@aws-sdk/lib-dynamodb";
3
- import { BatchGetItemInput } from "@aws-sdk/client-dynamodb";
4
-
5
- export class BatchGetBuilder<T> {
6
- /**
7
- * @param parent - The BetterDDB instance for the table.
8
- * @param keys - An array of partial keys for the items you wish to retrieve.
9
- */
10
- constructor(
11
- private parent: BetterDDB<T>,
12
- private keys: Partial<T>[],
13
- ) {}
14
-
15
- /**
16
- * Executes the batch get operation.
17
- * Returns an array of parsed items of type T.
18
- */
19
- public async execute(): Promise<T[]> {
20
- if (this.keys.length === 0) {
21
- return [];
22
- }
23
-
24
- const seen = new Set();
25
- const deduplicatedKeys = this.keys.filter((key) => {
26
- const keyString = JSON.stringify(key);
27
- if (seen.has(keyString)) {
28
- return false;
29
- }
30
- seen.add(keyString);
31
- return true;
32
- });
33
- const tableName = this.parent.getTableName();
34
- // Build an array of keys using the parent's key builder.
35
- const keysArray = deduplicatedKeys.map((key) => this.parent.buildKey(key));
36
-
37
- // Construct the BatchGet parameters.
38
- const params: BatchGetItemInput = {
39
- RequestItems: {
40
- [tableName]: {
41
- Keys: keysArray,
42
- },
43
- },
44
- };
45
-
46
- const result = await this.parent
47
- .getClient()
48
- .send(new BatchGetCommand(params));
49
- const responses = result.Responses ? result.Responses[tableName] : [];
50
- if (!responses) {
51
- return [];
52
- }
53
-
54
- return this.parent.getSchema().array().parse(responses) as T[];
55
- }
56
- }
@@ -1,97 +0,0 @@
1
- import {
2
- AttributeValue,
3
- Put,
4
- TransactWriteItem,
5
- } from "@aws-sdk/client-dynamodb";
6
- import { BetterDDB } from "../betterddb";
7
- import { PutCommand, TransactWriteCommand } from "@aws-sdk/lib-dynamodb";
8
-
9
- export class CreateBuilder<T> {
10
- private extraTransactItems: TransactWriteItem[] = [];
11
-
12
- constructor(
13
- private parent: BetterDDB<T>,
14
- private item: T,
15
- ) {}
16
-
17
- public async execute(): Promise<T> {
18
- const validated = this.parent.getSchema().parse(this.item);
19
- if (this.extraTransactItems.length > 0) {
20
- // Build our update transaction item.
21
- const myTransactItem = this.toTransactPut();
22
- // Combine with extra transaction items.
23
- const allItems = [...this.extraTransactItems, myTransactItem];
24
- await this.parent.getClient().send(
25
- new TransactWriteCommand({
26
- TransactItems: allItems,
27
- }),
28
- );
29
- // After transaction, retrieve the updated item.
30
- const result = await this.parent.get(this.item).execute();
31
- if (result === null) {
32
- throw new Error("Item not found after transaction create");
33
- }
34
- return result;
35
- } else {
36
- let finalItem: T = {
37
- ...this.item,
38
- entityType: this.parent.getEntityType(),
39
- };
40
- if (this.parent.getTimestamps()) {
41
- const now = new Date().toISOString();
42
- finalItem = { ...finalItem, createdAt: now, updatedAt: now } as T;
43
- }
44
-
45
- // Compute and merge primary key.
46
- const computedKeys = this.parent.buildKey(validated as Partial<T>);
47
- finalItem = { ...finalItem, ...computedKeys };
48
-
49
- // Compute and merge index attributes.
50
- const indexAttributes = this.parent.buildIndexes(validated as Partial<T>);
51
- finalItem = { ...finalItem, ...indexAttributes };
52
-
53
- await this.parent.getClient().send(
54
- new PutCommand({
55
- TableName: this.parent.getTableName(),
56
- Item: finalItem as Record<string, AttributeValue>,
57
- }),
58
- );
59
-
60
- return validated as T;
61
- }
62
- }
63
-
64
- public transactWrite(ops: TransactWriteItem[] | TransactWriteItem): this {
65
- if (Array.isArray(ops)) {
66
- this.extraTransactItems.push(...ops);
67
- } else {
68
- this.extraTransactItems.push(ops);
69
- }
70
- return this;
71
- }
72
-
73
- public toTransactPut(): TransactWriteItem {
74
- const validated = this.parent.getSchema().parse(this.item);
75
- let finalItem: T = {
76
- ...this.item,
77
- entityType: this.parent.getEntityType(),
78
- };
79
- if (this.parent.getTimestamps()) {
80
- const now = new Date().toISOString();
81
- finalItem = { ...finalItem, createdAt: now, updatedAt: now } as T;
82
- }
83
-
84
- // Compute and merge primary key.
85
- const computedKeys = this.parent.buildKey(validated as Partial<T>);
86
- finalItem = { ...finalItem, ...computedKeys };
87
-
88
- // Compute and merge index attributes.
89
- const indexAttributes = this.parent.buildIndexes(validated as Partial<T>);
90
- finalItem = { ...finalItem, ...indexAttributes };
91
- const putItem: Put = {
92
- TableName: this.parent.getTableName(),
93
- Item: finalItem as Record<string, AttributeValue>,
94
- };
95
- return { Put: putItem };
96
- }
97
- }
@@ -1,80 +0,0 @@
1
- import { BetterDDB } from "../betterddb";
2
- import { TransactWriteItem, DeleteItemInput } from "@aws-sdk/client-dynamodb";
3
- import { TransactWriteCommand, DeleteCommand } from "@aws-sdk/lib-dynamodb";
4
- export class DeleteBuilder<T> {
5
- private condition?: {
6
- expression: string;
7
- attributeValues: Record<string, any>;
8
- };
9
- private extraTransactItems: TransactWriteItem[] = [];
10
- constructor(
11
- private parent: BetterDDB<T>,
12
- private key: Partial<T>,
13
- ) {}
14
-
15
- /**
16
- * Specify a condition expression for the delete operation.
17
- */
18
- public withCondition(
19
- expression: string,
20
- attributeValues: Record<string, any>,
21
- ): this {
22
- if (this.condition) {
23
- this.condition.expression += ` AND ${expression}`;
24
- Object.assign(this.condition.attributeValues, attributeValues);
25
- } else {
26
- this.condition = { expression, attributeValues };
27
- }
28
- return this;
29
- }
30
-
31
- public async execute(): Promise<void> {
32
- if (this.extraTransactItems.length > 0) {
33
- // Build our update transaction item.
34
- const myTransactItem = this.toTransactDelete();
35
- // Combine with extra transaction items.
36
- const allItems = [...this.extraTransactItems, myTransactItem];
37
- await this.parent.getClient().send(
38
- new TransactWriteCommand({
39
- TransactItems: allItems,
40
- }),
41
- );
42
- // After transaction, retrieve the updated item.
43
- const result = await this.parent.get(this.key).execute();
44
- if (result === null) {
45
- throw new Error("Item not found after transaction delete");
46
- }
47
- } else {
48
- const params: DeleteItemInput = {
49
- TableName: this.parent.getTableName(),
50
- Key: this.parent.buildKey(this.key),
51
- };
52
- if (this.condition) {
53
- params.ConditionExpression = this.condition.expression;
54
- params.ExpressionAttributeValues = this.condition.attributeValues;
55
- }
56
- await this.parent.getClient().send(new DeleteCommand(params));
57
- }
58
- }
59
-
60
- public transactWrite(ops: TransactWriteItem[] | TransactWriteItem): this {
61
- if (Array.isArray(ops)) {
62
- this.extraTransactItems.push(...ops);
63
- } else {
64
- this.extraTransactItems.push(ops);
65
- }
66
- return this;
67
- }
68
-
69
- public toTransactDelete(): TransactWriteItem {
70
- const deleteItem: DeleteItemInput = {
71
- TableName: this.parent.getTableName(),
72
- Key: this.parent.buildKey(this.key),
73
- };
74
- if (this.condition) {
75
- deleteItem.ConditionExpression = this.condition.expression;
76
- deleteItem.ExpressionAttributeValues = this.condition.attributeValues;
77
- }
78
- return { Delete: deleteItem };
79
- }
80
- }