betterddb 0.3.0 → 0.4.1

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.
@@ -1,5 +1,6 @@
1
1
  import { BetterDDB } from '../betterddb';
2
- export declare class QueryBuilder<T> {
2
+ import { z } from 'zod';
3
+ export declare class QueryBuilder<S extends z.ZodType<any>> {
3
4
  private parent;
4
5
  private key;
5
6
  private filters;
@@ -10,18 +11,18 @@ export declare class QueryBuilder<T> {
10
11
  private limit?;
11
12
  private lastKey?;
12
13
  private ascending;
13
- constructor(parent: BetterDDB<T>, key: Partial<T>);
14
+ constructor(parent: BetterDDB<S>, key: Partial<S>);
14
15
  usingIndex(indexName: string): this;
15
16
  sortAscending(): this;
16
17
  sortDescending(): this;
17
- where(attribute: keyof T, operator: 'eq' | 'begins_with' | 'between', values: any | [any, any]): this;
18
+ where(attribute: keyof S, operator: 'eq' | 'begins_with' | 'between', values: any | [any, any]): this;
18
19
  limitResults(limit: number): this;
19
20
  startFrom(lastKey: Record<string, any>): this;
20
21
  /**
21
22
  * Executes the query and returns a Promise that resolves with an array of items.
22
23
  */
23
- execute(): Promise<T[]>;
24
- then<TResult1 = T[], TResult2 = never>(onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
25
- catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<T[] | TResult>;
26
- finally(onfinally?: (() => void) | null): Promise<T[]>;
24
+ execute(): Promise<S[]>;
25
+ then<TResult1 = S[], TResult2 = never>(onfulfilled?: ((value: S[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
26
+ catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<S[] | TResult>;
27
+ finally(onfinally?: (() => void) | null): Promise<S[]>;
27
28
  }
@@ -90,7 +90,7 @@ class QueryBuilder {
90
90
  params.FilterExpression = this.filters.join(' AND ');
91
91
  }
92
92
  const result = await this.parent.getClient().query(params).promise();
93
- return (result.Items || []).map(item => this.parent.getSchema().parse(item));
93
+ return this.parent.getSchema().array().parse(result.Items);
94
94
  }
95
95
  // Thenable implementation.
96
96
  then(onfulfilled, onrejected) {
@@ -1,20 +1,21 @@
1
1
  import { BetterDDB } from '../betterddb';
2
- export declare class ScanBuilder<T> {
2
+ import { z } from 'zod';
3
+ export declare class ScanBuilder<S extends z.ZodType<any>> {
3
4
  private parent;
4
5
  private filters;
5
6
  private expressionAttributeNames;
6
7
  private expressionAttributeValues;
7
8
  private limit?;
8
9
  private lastKey?;
9
- constructor(parent: BetterDDB<T>);
10
- where(attribute: keyof T, operator: 'eq' | 'begins_with' | 'between', values: any | [any, any]): this;
10
+ constructor(parent: BetterDDB<S>);
11
+ where(attribute: keyof S, operator: 'eq' | 'begins_with' | 'between', values: any | [any, any]): this;
11
12
  limitResults(limit: number): this;
12
13
  startFrom(lastKey: Record<string, any>): this;
13
14
  /**
14
15
  * Executes the scan and returns a Promise that resolves with an array of items.
15
16
  */
16
- execute(): Promise<T[]>;
17
- then<TResult1 = T[], TResult2 = never>(onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
18
- catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<T[] | TResult>;
19
- finally(onfinally?: (() => void) | null): Promise<T[]>;
17
+ execute(): Promise<S[]>;
18
+ then<TResult1 = S[], TResult2 = never>(onfulfilled?: ((value: S[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
19
+ catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<S[] | TResult>;
20
+ finally(onfinally?: (() => void) | null): Promise<S[]>;
20
21
  }
@@ -60,7 +60,7 @@ class ScanBuilder {
60
60
  params.FilterExpression = this.filters.join(' AND ');
61
61
  }
62
62
  const result = await this.parent.getClient().scan(params).promise();
63
- return (result.Items || []).map(item => this.parent.getSchema().parse(item));
63
+ return this.parent.getSchema().array().parse(result.Items);
64
64
  }
65
65
  // Thenable implementation.
66
66
  then(onfulfilled, onrejected) {
@@ -1,17 +1,18 @@
1
1
  import { DynamoDB } from 'aws-sdk';
2
2
  import { BetterDDB } from '../betterddb';
3
- export declare class UpdateBuilder<T> {
3
+ import { z } from 'zod';
4
+ export declare class UpdateBuilder<S extends z.ZodType<any>> {
4
5
  private parent;
5
6
  private key;
6
7
  private actions;
7
8
  private condition?;
8
9
  private expectedVersion?;
9
10
  private extraTransactItems;
10
- constructor(parent: BetterDDB<T>, key: Partial<T>, expectedVersion?: number);
11
- set(attrs: Partial<T>): this;
12
- remove(attrs: (keyof T)[]): this;
13
- add(attrs: Partial<Record<keyof T, number | Set<any>>>): this;
14
- delete(attrs: Partial<Record<keyof T, Set<any>>>): this;
11
+ constructor(parent: BetterDDB<S>, key: Partial<S>, expectedVersion?: number);
12
+ set(attrs: Partial<S>): this;
13
+ remove(attrs: (keyof S)[]): this;
14
+ add(attrs: Partial<Record<keyof S, number | Set<any>>>): this;
15
+ delete(attrs: Partial<Record<keyof S, Set<any>>>): this;
15
16
  /**
16
17
  * Adds a condition expression to the update.
17
18
  */
@@ -31,5 +32,5 @@ export declare class UpdateBuilder<T> {
31
32
  /**
32
33
  * Commits the update immediately by calling the parent's update method.
33
34
  */
34
- execute(): Promise<T>;
35
+ execute(): Promise<S>;
35
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "betterddb",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "A definition-based DynamoDB wrapper library that provides a schema-driven and fully typesafe DAL.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
package/src/betterddb.ts CHANGED
@@ -1,5 +1,4 @@
1
- // src/dynamo-dal.ts
2
- import { z, ZodSchema } from 'zod';
1
+ import { z } from 'zod';
3
2
  import { DynamoDB } from 'aws-sdk';
4
3
  import { QueryBuilder } from './builders/query-builder';
5
4
  import { ScanBuilder } from './builders/scan-builder';
@@ -8,117 +7,79 @@ import { CreateBuilder } from './builders/create-builder';
8
7
  import { GetBuilder } from './builders/get-builder';
9
8
  import { DeleteBuilder } from './builders/delete-builder';
10
9
 
10
+ export type SchemaType<S extends z.ZodType<any>> = z.infer<S>;
11
+
11
12
  export type PrimaryKeyValue = string | number;
12
13
 
13
- /**
14
- * A key definition can be either a simple key (a property name)
15
- * or an object containing a build function that computes the value.
16
- * (In this design, the attribute name is provided separately.)
17
- */
18
- export type KeyDefinition<T> =
19
- | keyof T
14
+ export type KeyDefinition<S extends z.ZodType<any>> =
15
+ | keyof SchemaType<S>
20
16
  | {
21
- build: (rawKey: Partial<T>) => string;
17
+ build: (rawKey: Partial<SchemaType<S>>) => string;
22
18
  };
23
19
 
24
- /**
25
- * Configuration for a primary (partition) key.
26
- */
27
- export interface PrimaryKeyConfig<T> {
28
- /** The attribute name for the primary key in DynamoDB */
20
+ export interface PrimaryKeyConfig<S extends z.ZodType<any>> {
29
21
  name: string;
30
- /** How to compute the key value; if a keyof T, then the raw value is used;
31
- * if an object, the build function is used.
32
- */
33
- definition: KeyDefinition<T>;
22
+ definition: KeyDefinition<S>;
34
23
  }
35
24
 
36
- /**
37
- * Configuration for a sort key.
38
- */
39
- export interface SortKeyConfig<T> {
40
- /** The attribute name for the sort key in DynamoDB */
25
+ export interface SortKeyConfig<S extends z.ZodType<any>> {
41
26
  name: string;
42
- /** How to compute the sort key value */
43
- definition: KeyDefinition<T>;
27
+ definition: KeyDefinition<S>;
44
28
  }
45
29
 
46
- /**
47
- * Configuration for a Global Secondary Index (GSI).
48
- */
49
- export interface GSIConfig<T> {
50
- /** The name of the GSI in DynamoDB */
30
+ export interface GSIConfig<S extends z.ZodType<any>> {
51
31
  name: string;
52
- /** The primary key configuration for the GSI */
53
- primary: PrimaryKeyConfig<T>;
54
- /** The sort key configuration for the GSI, if any */
55
- sort?: SortKeyConfig<T>;
32
+ primary: PrimaryKeyConfig<S>;
33
+ sort?: SortKeyConfig<S>;
56
34
  }
57
35
 
58
- /**
59
- * Keys configuration for the table.
60
- */
61
- export interface KeysConfig<T> {
62
- primary: PrimaryKeyConfig<T>;
63
- sort?: SortKeyConfig<T>;
36
+ export interface KeysConfig<S extends z.ZodType<any>> {
37
+ primary: PrimaryKeyConfig<S>;
38
+ sort?: SortKeyConfig<S>;
64
39
  gsis?: {
65
- [gsiName: string]: GSIConfig<T>;
40
+ [gsiName: string]: GSIConfig<S>;
66
41
  };
67
42
  }
68
43
 
69
- /**
70
- * Options for initializing BetterDDB.
71
- */
72
- export interface BetterDDBOptions<T> {
73
- schema: ZodSchema<T>;
44
+ export interface BetterDDBOptions<S extends z.ZodType<any>> {
45
+ schema: S;
74
46
  tableName: string;
75
47
  entityName: string;
76
- keys: KeysConfig<T>;
48
+ keys: KeysConfig<S>;
77
49
  client: DynamoDB.DocumentClient;
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
50
  autoTimestamps?: boolean;
86
51
  }
87
52
 
88
- /**
89
- * BetterDDB is a definition-based DynamoDB wrapper library.
90
- */
91
- export class BetterDDB<T> {
92
- protected schema: ZodSchema<T>;
53
+ export class BetterDDB<S extends z.ZodType<any>> {
54
+ protected schema: S;
93
55
  protected tableName: string;
94
56
  protected entityName: string;
95
57
  protected client: DynamoDB.DocumentClient;
96
- protected keys: KeysConfig<T>;
58
+ protected keys: KeysConfig<S>;
97
59
  protected autoTimestamps: boolean;
98
60
 
99
- constructor(options: BetterDDBOptions<T>) {
61
+ constructor(options: BetterDDBOptions<S>) {
100
62
  this.schema = options.schema;
101
63
  this.tableName = options.tableName;
102
64
  this.entityName = options.entityName.toUpperCase();
103
- this.keys = options.keys;
104
65
  this.client = options.client;
105
66
  this.autoTimestamps = options.autoTimestamps ?? false;
67
+ this.keys = options.keys;
106
68
  }
107
69
 
108
- public getKeys(): KeysConfig<T> {
70
+ public getKeys(): KeysConfig<S> {
109
71
  return this.keys;
110
72
  }
111
-
73
+
112
74
  public getTableName(): string {
113
75
  return this.tableName;
114
76
  }
115
-
77
+
116
78
  public getClient(): DynamoDB.DocumentClient {
117
79
  return this.client;
118
80
  }
119
-
120
-
121
- public getSchema(): ZodSchema<T> {
81
+
82
+ public getSchema(): S {
122
83
  return this.schema;
123
84
  }
124
85
 
@@ -126,118 +87,70 @@ export class BetterDDB<T> {
126
87
  return this.autoTimestamps;
127
88
  }
128
89
 
129
- // Helper: Retrieve the key value from a KeyDefinition.
130
- protected getKeyValue(def: KeyDefinition<T>, rawKey: Partial<T>): string {
90
+ protected getKeyValue(
91
+ def: KeyDefinition<S>,
92
+ rawKey: Partial<SchemaType<S>>
93
+ ): string {
131
94
  if (typeof def === 'string' || typeof def === 'number' || typeof def === 'symbol') {
132
- return String(rawKey[def]);
133
- } else {
134
- return def.build(rawKey);
95
+ return String(rawKey[def as keyof typeof rawKey]);
135
96
  }
97
+ return def.build(rawKey);
136
98
  }
137
99
 
138
- /**
139
- * Build the primary key from a raw key object.
140
- */
141
- public buildKey(rawKey: Partial<T>): Record<string, any> {
100
+ public buildKey(rawKey: Partial<SchemaType<S>>): Record<string, any> {
142
101
  const keyObj: Record<string, any> = {};
143
-
144
- // For primary (partition) key:
145
- const pkConfig = this.keys.primary;
146
- keyObj[pkConfig.name] =
147
- (typeof pkConfig.definition === 'string' ||
148
- typeof pkConfig.definition === 'number' ||
149
- typeof pkConfig.definition === 'symbol')
150
- ? String((rawKey as any)[pkConfig.definition])
151
- : pkConfig.definition.build(rawKey);
152
-
153
- // For sort key, if defined:
154
- if (this.keys.sort) {
155
- const skConfig = this.keys.sort;
156
- keyObj[skConfig.name] =
157
- (typeof skConfig.definition === 'string' ||
158
- typeof skConfig.definition === 'number' ||
159
- typeof skConfig.definition === 'symbol')
160
- ? String((rawKey as any)[skConfig.definition])
161
- : skConfig.definition.build(rawKey);
102
+ const { primary, sort } = this.keys;
103
+
104
+ keyObj[primary.name] = this.getKeyValue(primary.definition, rawKey);
105
+
106
+ if (sort) {
107
+ keyObj[sort.name] = this.getKeyValue(sort.definition, rawKey);
162
108
  }
109
+
163
110
  return keyObj;
164
111
  }
165
-
166
- /**
167
- * Build index attributes for each defined GSI.
168
- */
169
- public buildIndexes(rawItem: Partial<T>): Record<string, any> {
112
+
113
+ public buildIndexes(rawItem: Partial<SchemaType<S>>): Record<string, any> {
170
114
  const indexAttributes: Record<string, any> = {};
115
+
171
116
  if (this.keys.gsis) {
172
- for (const gsiName in this.keys.gsis) {
173
- const gsiConfig = this.keys.gsis[gsiName];
174
-
175
- // Compute primary index attribute.
176
- const primaryConfig = gsiConfig.primary;
177
- indexAttributes[primaryConfig.name] =
178
- (typeof primaryConfig.definition === 'string' ||
179
- typeof primaryConfig.definition === 'number' ||
180
- typeof primaryConfig.definition === 'symbol')
181
- ? String((rawItem as any)[primaryConfig.definition])
182
- : primaryConfig.definition.build(rawItem);
183
-
184
- // Compute sort index attribute if provided.
185
- if (gsiConfig.sort) {
186
- const sortConfig = gsiConfig.sort;
187
- indexAttributes[sortConfig.name] =
188
- (typeof sortConfig.definition === 'string' ||
189
- typeof sortConfig.definition === 'number' ||
190
- typeof sortConfig.definition === 'symbol')
191
- ? String((rawItem as any)[sortConfig.definition])
192
- : sortConfig.definition.build(rawItem);
117
+ Object.entries(this.keys.gsis).forEach(([_, gsiConfig]) => {
118
+ const { primary, sort } = gsiConfig;
119
+ indexAttributes[primary.name] = this.getKeyValue(primary.definition, rawItem);
120
+
121
+ if (sort) {
122
+ indexAttributes[sort.name] = this.getKeyValue(sort.definition, rawItem);
193
123
  }
194
- }
124
+ });
195
125
  }
126
+
196
127
  return indexAttributes;
197
128
  }
198
-
199
- /**
200
- * Create an item:
201
- * - Computes primary key and index attributes,
202
- * - Optionally injects timestamps,
203
- * - Validates the item and writes it to DynamoDB.
204
- */
205
- public create(item: T): CreateBuilder<T> {
206
- return new CreateBuilder<T>(this, item);
129
+
130
+ public create(item: SchemaType<S>): CreateBuilder<SchemaType<S>> {
131
+ return new CreateBuilder<SchemaType<S>>(this, item);
207
132
  }
208
-
209
- /**
210
- * Get an item by its primary key.
211
- */
212
- public get(rawKey: Partial<T>): GetBuilder<T> {
213
- return new GetBuilder<T>(this, rawKey);
133
+
134
+ public get(rawKey: Partial<SchemaType<S>>): GetBuilder<SchemaType<S>> {
135
+ return new GetBuilder<SchemaType<S>>(this, rawKey);
214
136
  }
215
137
 
216
- /**
217
- * Update an item.
218
- */
219
- public update(key: Partial<T>, expectedVersion?: number): UpdateBuilder<T> {
220
- return new UpdateBuilder<T>(this, key, expectedVersion);
138
+ public update(
139
+ key: Partial<SchemaType<S>>,
140
+ expectedVersion?: number
141
+ ): UpdateBuilder<SchemaType<S>> {
142
+ return new UpdateBuilder<SchemaType<S>>(this, key, expectedVersion);
221
143
  }
222
144
 
223
- /**
224
- * Delete an item.
225
- */
226
- public delete(rawKey: Partial<T>): DeleteBuilder<T> {
227
- return new DeleteBuilder<T>(this, rawKey);
145
+ public delete(rawKey: Partial<SchemaType<S>>): DeleteBuilder<SchemaType<S>> {
146
+ return new DeleteBuilder<SchemaType<S>>(this, rawKey);
228
147
  }
229
148
 
230
- /**
231
- * Query items.
232
- */
233
- public query(key: Partial<T>): QueryBuilder<T> {
234
- return new QueryBuilder<T>(this, key);
149
+ public query(key: Partial<SchemaType<S>>): QueryBuilder<SchemaType<S>> {
150
+ return new QueryBuilder<SchemaType<S>>(this, key);
235
151
  }
236
152
 
237
- /**
238
- * Scan for items.
239
- */
240
- public scan(): ScanBuilder<T> {
241
- return new ScanBuilder<T>(this);
153
+ public scan(): ScanBuilder<SchemaType<S>> {
154
+ return new ScanBuilder<SchemaType<S>>(this);
242
155
  }
243
- }
156
+ }
@@ -1,12 +1,13 @@
1
1
  import { DynamoDB } from 'aws-sdk';
2
2
  import { BetterDDB } from '../betterddb';
3
+ import { z } from 'zod';
3
4
 
4
- export class CreateBuilder<T> {
5
+ export class CreateBuilder<S extends z.ZodType<any>> {
5
6
  private extraTransactItems: DynamoDB.DocumentClient.TransactWriteItemList = [];
6
7
 
7
- constructor(private parent: BetterDDB<T>, private item: T) {}
8
+ constructor(private parent: BetterDDB<S>, private item: S) {}
8
9
 
9
- public async execute(): Promise<T> {
10
+ public async execute(): Promise<S> {
10
11
  if (this.extraTransactItems.length > 0) {
11
12
  // Build our update transaction item.
12
13
  const myTransactItem = this.toTransactPut();
@@ -25,7 +26,7 @@ export class CreateBuilder<T> {
25
26
  let item = this.item;
26
27
  if (this.parent.getAutoTimestamps()) {
27
28
  const now = new Date().toISOString();
28
- item = { ...item, createdAt: now, updatedAt: now } as T;
29
+ item = { ...item, createdAt: now, updatedAt: now } as S;
29
30
  }
30
31
  // Validate the item using the schema.
31
32
  const validated = this.parent.getSchema().parse(item);
@@ -65,18 +66,18 @@ export class CreateBuilder<T> {
65
66
  return { Put: putItem };
66
67
  }
67
68
 
68
- public then<TResult1 = T, TResult2 = never>(
69
- onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
69
+ public then<TResult1 = S, TResult2 = never>(
70
+ onfulfilled?: ((value: S) => TResult1 | PromiseLike<TResult1>) | null,
70
71
  onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
71
72
  ): Promise<TResult1 | TResult2> {
72
73
  return this.execute().then(onfulfilled, onrejected);
73
74
  }
74
75
  public catch<TResult = never>(
75
76
  onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
76
- ): Promise<T | TResult> {
77
+ ): Promise<S | TResult> {
77
78
  return this.execute().catch(onrejected);
78
79
  }
79
- public finally(onfinally?: (() => void) | null): Promise<T> {
80
+ public finally(onfinally?: (() => void) | null): Promise<S> {
80
81
  return this.execute().finally(onfinally);
81
82
  }
82
83
  }
@@ -1,10 +1,11 @@
1
1
  import { DynamoDB } from 'aws-sdk';
2
2
  import { BetterDDB } from '../betterddb';
3
+ import { z } from 'zod';
3
4
 
4
- export class DeleteBuilder<T> {
5
+ export class DeleteBuilder<S extends z.ZodType<any>> {
5
6
  private condition?: { expression: string; attributeValues: Record<string, any> };
6
7
  private extraTransactItems: DynamoDB.DocumentClient.TransactWriteItemList = [];
7
- constructor(private parent: BetterDDB<T>, private key: Partial<T>) {}
8
+ constructor(private parent: BetterDDB<S>, private key: Partial<S>) {}
8
9
 
9
10
  /**
10
11
  * Specify a condition expression for the delete operation.
@@ -1,16 +1,17 @@
1
1
  import { DynamoDB } from 'aws-sdk';
2
2
  import { BetterDDB } from '../betterddb';
3
+ import { z } from 'zod';
3
4
 
4
- export class GetBuilder<T> {
5
+ export class GetBuilder<S extends z.ZodType<any>> {
5
6
  private projectionExpression?: string;
6
7
  private expressionAttributeNames: Record<string, string> = {};
7
8
  private extraTransactItems: DynamoDB.DocumentClient.TransactGetItemList = [];
8
- constructor(private parent: BetterDDB<T>, private key: Partial<T>) {}
9
+ constructor(private parent: BetterDDB<S>, private key: Partial<S>) {}
9
10
 
10
11
  /**
11
12
  * Specify a projection by providing an array of attribute names.
12
13
  */
13
- public withProjection(attributes: (keyof T)[]): this {
14
+ public withProjection(attributes: (keyof S)[]): this {
14
15
  this.projectionExpression = attributes.map(attr => `#${String(attr)}`).join(', ');
15
16
  for (const attr of attributes) {
16
17
  this.expressionAttributeNames[`#${String(attr)}`] = String(attr);
@@ -18,7 +19,7 @@ export class GetBuilder<T> {
18
19
  return this;
19
20
  }
20
21
 
21
- public async execute(): Promise<T | null> {
22
+ public async execute(): Promise<S | null> {
22
23
  if (this.extraTransactItems.length > 0) {
23
24
  // Build our update transaction item.
24
25
  const myTransactItem = this.toTransactGet();
@@ -66,18 +67,18 @@ export class GetBuilder<T> {
66
67
  return { Get: getItem };
67
68
  }
68
69
 
69
- public then<TResult1 = T | null, TResult2 = never>(
70
- onfulfilled?: ((value: T | null) => TResult1 | PromiseLike<TResult1>) | null,
70
+ public then<TResult1 = S | null, TResult2 = never>(
71
+ onfulfilled?: ((value: S | null) => TResult1 | PromiseLike<TResult1>) | null,
71
72
  onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
72
73
  ): Promise<TResult1 | TResult2> {
73
74
  return this.execute().then(onfulfilled, onrejected);
74
75
  }
75
76
  public catch<TResult = never>(
76
77
  onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
77
- ): Promise<T | null | TResult> {
78
+ ): Promise<S | null | TResult> {
78
79
  return this.execute().catch(onrejected);
79
80
  }
80
- public finally(onfinally?: (() => void) | null): Promise<T | null> {
81
+ public finally(onfinally?: (() => void) | null): Promise<S | null> {
81
82
  return this.execute().finally(onfinally);
82
83
  }
83
84
  }
@@ -1,7 +1,8 @@
1
1
  import { DynamoDB } from 'aws-sdk';
2
2
  import { BetterDDB } from '../betterddb';
3
+ import { z } from 'zod';
3
4
 
4
- export class QueryBuilder<T> {
5
+ export class QueryBuilder<S extends z.ZodType<any>> {
5
6
  private filters: string[] = [];
6
7
  private expressionAttributeNames: Record<string, string> = {};
7
8
  private expressionAttributeValues: Record<string, any> = {};
@@ -11,7 +12,7 @@ export class QueryBuilder<T> {
11
12
  private lastKey?: Record<string, any>;
12
13
  private ascending: boolean = true;
13
14
 
14
- constructor(private parent: BetterDDB<T>, private key: Partial<T>) {}
15
+ constructor(private parent: BetterDDB<S>, private key: Partial<S>) {}
15
16
 
16
17
  public usingIndex(indexName: string): this {
17
18
  this.indexName = indexName;
@@ -29,7 +30,7 @@ export class QueryBuilder<T> {
29
30
  }
30
31
 
31
32
  public where(
32
- attribute: keyof T,
33
+ attribute: keyof S,
33
34
  operator: 'eq' | 'begins_with' | 'between',
34
35
  values: any | [any, any]
35
36
  ): this {
@@ -73,7 +74,7 @@ export class QueryBuilder<T> {
73
74
  /**
74
75
  * Executes the query and returns a Promise that resolves with an array of items.
75
76
  */
76
- public async execute(): Promise<T[]> {
77
+ public async execute(): Promise<S[]> {
77
78
  // Build a simple key condition for the partition key.
78
79
  const keys = this.parent.getKeys();
79
80
  const pkName = keys.primary.name;
@@ -106,22 +107,22 @@ export class QueryBuilder<T> {
106
107
  }
107
108
 
108
109
  const result = await this.parent.getClient().query(params).promise();
109
- return (result.Items || []).map(item => this.parent.getSchema().parse(item));
110
+ return this.parent.getSchema().array().parse(result.Items);
110
111
  }
111
112
 
112
113
  // Thenable implementation.
113
- public then<TResult1 = T[], TResult2 = never>(
114
- onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null,
114
+ public then<TResult1 = S[], TResult2 = never>(
115
+ onfulfilled?: ((value: S[]) => TResult1 | PromiseLike<TResult1>) | null,
115
116
  onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
116
117
  ): Promise<TResult1 | TResult2> {
117
118
  return this.execute().then(onfulfilled, onrejected);
118
119
  }
119
120
  public catch<TResult = never>(
120
121
  onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
121
- ): Promise<T[] | TResult> {
122
+ ): Promise<S[] | TResult> {
122
123
  return this.execute().catch(onrejected);
123
124
  }
124
- public finally(onfinally?: (() => void) | null): Promise<T[]> {
125
+ public finally(onfinally?: (() => void) | null): Promise<S[]> {
125
126
  return this.execute().finally(onfinally);
126
127
  }
127
128
  }