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 +228 -0
- package/package.json +14 -46
- package/dist/index.cjs +0 -1081
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -454
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts +0 -454
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -1045
- package/dist/index.mjs.map +0 -1
- package/dist/streams.cjs +0 -84
- package/dist/streams.cjs.map +0 -1
- package/dist/streams.d.cts +0 -21
- package/dist/streams.d.cts.map +0 -1
- package/dist/streams.d.mts +0 -21
- package/dist/streams.d.mts.map +0 -1
- package/dist/streams.mjs +0 -82
- package/dist/streams.mjs.map +0 -1
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
|
-
"@
|
|
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
|
-
|
|
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
|
-
"
|
|
80
|
-
|
|
43
|
+
"version": "0.1.2",
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "vp run --recursive build",
|
|
46
|
+
"test": "vp run --recursive test"
|
|
47
|
+
}
|
|
48
|
+
}
|