dinah 0.1.0 → 0.1.2

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
@@ -0,0 +1,228 @@
1
+ <p align="center">
2
+ <img src="dinah-logo.png" alt="dinah" width="300" />
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/dinah"><img src="https://img.shields.io/npm/v/dinah.svg" alt="npm version" /></a>
7
+ <a href="https://github.com/jayalfredprufrock/dinah/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/dinah.svg" alt="license" /></a>
8
+ </p>
9
+
10
+ [In my world](https://youtu.be/UD8hATR4B8s?si=HvSFmgrrxwucR91x&t=68), working with DynamoDb would be painless, safe, and fun. Dinah provides a type-safe, expressive API for interacting with DynamoDB, featuring schema-driven table definitions, a repository pattern with full type inference, MongoDB-like query syntax, and first-class support for batch operations, transactions, pagination, and GSIs. It is closer to a query builder than an ORM, and doesn't encourage single-table design. Nonsense? That's for you to decide.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install dinah @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ### Create a Db Instance
21
+
22
+ `Db` wraps the AWS SDK v3 DynamoDB client. Pass it a `DynamoDBClient`, a `DynamoDBClientConfig`, or an existing client instance:
23
+
24
+ ```typescript
25
+ import { Db } from "dinah";
26
+
27
+ const db = new Db({ region: "us-east-1" });
28
+ ```
29
+
30
+ ### Using the Db Class Directly
31
+
32
+ The `Db` class exposes low-level operations that work with any table by name:
33
+
34
+ ```typescript
35
+ // Put an item
36
+ await db.put({
37
+ table: "users",
38
+ item: {
39
+ userId: "u1",
40
+ email: "alice@example.com",
41
+ name: "Alice",
42
+ role: "admin",
43
+ createdAt: Date.now(),
44
+ },
45
+ });
46
+
47
+ // Get an item
48
+ const user = await db.get<{ userId: string; name: string }>({
49
+ table: "users",
50
+ key: { userId: "u1" },
51
+ });
52
+
53
+ // Query
54
+ const users = await db.query<{ userId: string; name: string }>({
55
+ table: "users",
56
+ query: { role: "admin" },
57
+ index: "byRole",
58
+ });
59
+
60
+ // Update
61
+ await db.update({
62
+ table: "users",
63
+ key: { userId: "u1" },
64
+ update: { name: "Alice Smith", updatedAt: Date.now() },
65
+ });
66
+
67
+ // Delete
68
+ await db.delete({ table: "users", key: { userId: "u1" } });
69
+ ```
70
+
71
+ ### Define a Table
72
+
73
+ Use [TypeBox](https://github.com/sinclairzx81/typebox) schemas to define your table's shape, then pass it to `Table` along with key configuration:
74
+
75
+ ```typescript
76
+ import { Type } from "typebox";
77
+ import { Table } from "dinah";
78
+
79
+ const UserTable = new Table(
80
+ Type.Object({
81
+ userId: Type.String(),
82
+ email: Type.String(),
83
+ name: Type.String(),
84
+ role: Type.String(),
85
+ createdAt: Type.Number(),
86
+ updatedAt: Type.Optional(Type.Number()),
87
+ }),
88
+ {
89
+ name: "users",
90
+ partitionKey: "userId",
91
+ billingMode: "PAY_PER_REQUEST",
92
+ gsis: {
93
+ byEmail: { partitionKey: "email" },
94
+ byRole: { partitionKey: "role", sortKey: "createdAt" },
95
+ },
96
+ },
97
+ );
98
+ ```
99
+
100
+ ### Using the Repository Class
101
+
102
+ `Repository` is the recommended way to interact with DynamoDB. Created from a `Table`, it provides full type inference for keys, items, queries, and projections:
103
+
104
+ ```typescript
105
+ const userRepo = db.createRepo(UserTable);
106
+ ```
107
+
108
+ #### CRUD
109
+
110
+ ```typescript
111
+ // Create (conditional put - fails if item already exists)
112
+ const user = await userRepo.create({
113
+ userId: "u1",
114
+ email: "alice@example.com",
115
+ name: "Alice",
116
+ role: "admin",
117
+ createdAt: Date.now(),
118
+ });
119
+
120
+ // Get
121
+ const alice = await userRepo.get({ userId: "u1" });
122
+
123
+ // Get with projection (return type narrows to projected fields)
124
+ const partial = await userRepo.get({ userId: "u1" }, { projection: ["name", "email"] });
125
+
126
+ // getOrThrow (throws if item not found)
127
+ const aliceOrThrow = await userRepo.getOrThrow({ userId: "u1" });
128
+
129
+ // Update
130
+ const updated = await userRepo.update(
131
+ { userId: "u1" },
132
+ { name: "Alice Smith", updatedAt: Date.now() },
133
+ );
134
+
135
+ // Delete
136
+ await userRepo.delete({ userId: "u1" });
137
+ ```
138
+
139
+ #### Querying
140
+
141
+ Queries use a MongoDB-like syntax with operators like `$gt`, `$between`, `$prefix`, and more:
142
+
143
+ ```typescript
144
+ // Query by partition key
145
+ const admins = await userRepo.query({ userId: "u1" });
146
+
147
+ // Query a GSI
148
+ const adminsByDate = await userRepo.queryGsi("byRole", {
149
+ role: "admin",
150
+ createdAt: { $gt: 1700000000000 },
151
+ });
152
+
153
+ // Scan with filters
154
+ const recentUsers = await userRepo.scan({
155
+ filter: { createdAt: { $gte: 1700000000000 } },
156
+ });
157
+
158
+ // Paginated query with async iteration
159
+ for await (const page of userRepo.queryGsiPaged("byRole", { role: "admin" })) {
160
+ console.log(page); // each page is an array of items
161
+ }
162
+ ```
163
+
164
+ #### Batch Operations
165
+
166
+ ```typescript
167
+ // Batch get
168
+ const { items, unprocessed } = await userRepo.batchGet([
169
+ { userId: "u1" },
170
+ { userId: "u2" },
171
+ { userId: "u3" },
172
+ ]);
173
+
174
+ // Batch write (puts and deletes)
175
+ await userRepo.batchWrite([
176
+ {
177
+ type: "PUT",
178
+ item: {
179
+ userId: "u4",
180
+ email: "bob@example.com",
181
+ name: "Bob",
182
+ role: "user",
183
+ createdAt: Date.now(),
184
+ },
185
+ },
186
+ { type: "DELETE", key: { userId: "u3" } },
187
+ ]);
188
+ ```
189
+
190
+ #### Transactions
191
+
192
+ ```typescript
193
+ await userRepo.trxWrite(
194
+ userRepo.trxPut({
195
+ userId: "u5",
196
+ email: "eve@example.com",
197
+ name: "Eve",
198
+ role: "user",
199
+ createdAt: Date.now(),
200
+ }),
201
+ userRepo.trxUpdate({ userId: "u1" }, { role: "superadmin" }),
202
+ userRepo.trxDelete({ userId: "u2" }),
203
+ );
204
+ ```
205
+
206
+ ### Lifecycle Hooks
207
+
208
+ Tables support `beforePut` and `beforeUpdate` hooks for setting defaults like timestamps:
209
+
210
+ ```typescript
211
+ const UserTable = new Table(schema, {
212
+ name: "users",
213
+ partitionKey: "userId",
214
+ beforePut: (item) => ({
215
+ createdAt: Date.now(),
216
+ ...item,
217
+ updatedAt: Date.now(),
218
+ }),
219
+ beforeUpdate: (update) => ({
220
+ ...update,
221
+ updatedAt: Date.now(),
222
+ }),
223
+ });
224
+ ```
225
+
226
+ ## License
227
+
228
+ [MIT](LICENSE)
package/package.json CHANGED
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "name": "dinah",
3
- "version": "0.1.0",
4
3
  "description": "A dynamodb client designed for typescript",
5
4
  "homepage": "https://github.com/jayalfredprufrock/dinah#readme",
6
5
  "bugs": {
@@ -15,56 +14,15 @@
15
14
  "files": [
16
15
  "dist"
17
16
  ],
18
- "type": "module",
19
- "main": "./dist/index.cjs",
20
- "module": "./dist/index.mjs",
21
- "types": "./dist/index.d.cts",
22
- "exports": {
23
- ".": "./src/index.ts",
24
- "./streams": "./src/streams.ts",
25
- "./package.json": "./package.json"
26
- },
27
- "publishConfig": {
28
- "access": "public",
29
- "exports": {
30
- ".": {
31
- "import": "./dist/index.mjs",
32
- "require": "./dist/index.cjs"
33
- },
34
- "./streams": {
35
- "import": "./dist/streams.mjs",
36
- "require": "./dist/streams.cjs"
37
- },
38
- "./package.json": "./package.json"
39
- }
40
- },
41
- "scripts": {
42
- "dev": "vp pack --watch",
43
- "prepare": "vp config"
44
- },
45
17
  "devDependencies": {
46
- "@aws-sdk/client-dynamodb": "^3.1024.0",
47
- "@aws-sdk/lib-dynamodb": "^3.1024.0",
18
+ "@semantic-release/exec": "^7.1.0",
48
19
  "@tsconfig/node24": "^24.0.4",
49
20
  "@types/node": "^24.12.0",
50
21
  "@typescript/native-preview": "7.0.0-dev.20260328.1",
51
22
  "semantic-release": "^25.0.3",
52
23
  "typebox": "^1.1.14",
53
- "typescript": "^6.0.2",
54
24
  "vite-plus": "^0.1.14"
55
25
  },
56
- "peerDependencies": {
57
- "@aws-sdk/client-dynamodb": "^3.1024.0",
58
- "@aws-sdk/client-dynamodb-streams": "^3.1024.0",
59
- "@aws-sdk/lib-dynamodb": "^3.1024.0",
60
- "sift": "^17.1.3",
61
- "typebox": "^1.1.14"
62
- },
63
- "peerDependenciesMeta": {
64
- "@aws-sdk/client-dynamodb-streams": {
65
- "optional": true
66
- }
67
- },
68
26
  "release": {
69
27
  "branches": [
70
28
  "main"
@@ -72,9 +30,19 @@
72
30
  "plugins": [
73
31
  "@semantic-release/commit-analyzer",
74
32
  "@semantic-release/release-notes-generator",
75
- "@semantic-release/npm",
33
+ [
34
+ "@semantic-release/exec",
35
+ {
36
+ "prepareCmd": "pnpm version ${nextRelease.version} --git-tag-version=false",
37
+ "publishCmd": "pnpm --filter dinah publish --no-git-checks"
38
+ }
39
+ ],
76
40
  "@semantic-release/github"
77
41
  ]
78
42
  },
79
- "packageManager": "pnpm@10.33.0"
80
- }
43
+ "version": "0.1.2",
44
+ "scripts": {
45
+ "build": "vp run --recursive build",
46
+ "test": "vp run --recursive test"
47
+ }
48
+ }