dyno-table 0.0.1 → 0.0.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 ADDED
@@ -0,0 +1,344 @@
1
+ # 🦖 dyno-table
2
+
3
+ A powerful, type-safe, and fluent DynamoDB table abstraction layer for Node.js applications.
4
+
5
+ Allows you to work with DynamoDB in a single table design pattern
6
+
7
+ ## ✨ Features
8
+
9
+ - **Type-safe operations**: Ensures type safety for all DynamoDB operations.
10
+ - **Builders for operations**: Provides builders for put, update, delete, query, and scan operations.
11
+ - **Transaction support**: Supports transactional operations.
12
+ - **Batch operations**: Handles batch write operations with automatic chunking for large datasets.
13
+ - **Conditional operations**: Supports conditional puts, updates, and deletes.
14
+ - **Repository pattern**: Provides a base repository class for implementing the repository pattern.
15
+ - **Error handling**: Custom error classes for handling DynamoDB errors gracefully.
16
+
17
+ ## 📦 Installation
18
+
19
+ Get started with Dyno Table by installing it via npm:
20
+
21
+ ```bash
22
+ npm install dyno-table
23
+ ```
24
+
25
+ ## 🦕 Getting Started
26
+
27
+ ### Setting Up the Table
28
+
29
+ First, set up the `Table` instance with your DynamoDB client and table configuration.
30
+
31
+ ```ts
32
+ import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb";
33
+ import { Table } from "dyno-table";
34
+ import { docClient } from "./ddb-client"; // Your DynamoDB client instance
35
+
36
+ const table = new Table({
37
+ client: docClient,
38
+ tableName: "DinoTable",
39
+ tableIndexes: {
40
+ primary: {
41
+ pkName: "pk",
42
+ skName: "sk",
43
+ },
44
+ GSI1: {
45
+ pkName: "GSI1PK",
46
+ skName: "GSI1SK",
47
+ },
48
+ },
49
+ });
50
+ ```
51
+
52
+ ### CRUD Operations
53
+
54
+ #### Create (Put)
55
+
56
+ ```ts
57
+ // Simple put
58
+ const dino = {
59
+ pk: "SPECIES#trex",
60
+ sk: "PROFILE#001",
61
+ name: "Rex",
62
+ diet: "Carnivore",
63
+ length: 40,
64
+ type: "DINOSAUR",
65
+ };
66
+
67
+ await table.put(dino).execute();
68
+
69
+ // Conditional put
70
+ await table
71
+ .put(dino)
72
+ .whereNotExists("pk") // Only insert if dinosaur doesn't exist
73
+ .whereNotExists("sk")
74
+ .execute();
75
+ ```
76
+
77
+ #### Read (Get)
78
+
79
+ ```ts
80
+ const key = { pk: "SPECIES#trex", sk: "PROFILE#001" };
81
+ const result = await table.get(key);
82
+ console.log(result);
83
+
84
+ // Get with specific index
85
+ const result = await table.get(key, { indexName: "GSI1" });
86
+ ```
87
+
88
+ #### Update
89
+
90
+ ```ts
91
+ // Simple update
92
+ const updates = { length: 42, diet: "Carnivore" };
93
+ await table.update(key).setMany(updates).execute();
94
+
95
+ // Advanced update operations
96
+ await table
97
+ .update(key)
98
+ .set("diet", "Omnivore") // Set a single field
99
+ .set({ length: 45, name: "Rexy" }) // Set multiple fields
100
+ .remove("optional_field") // Remove fields
101
+ .increment("sightings", 1) // Increment a number
102
+ .whereEquals("length", 42) // Conditional update
103
+ .execute();
104
+ ```
105
+
106
+ #### Delete
107
+
108
+ ```ts
109
+ // Simple delete
110
+ await table.delete(key).execute();
111
+
112
+ // Conditional delete
113
+ await table
114
+ .delete(key)
115
+ .whereExists("pk")
116
+ .whereEquals("type", "DINOSAUR")
117
+ .execute();
118
+ ```
119
+
120
+ ### Query Operations
121
+
122
+ ```ts
123
+ // Basic query
124
+ const result = await table
125
+ .query({ pk: "SPECIES#trex" })
126
+ .execute();
127
+
128
+ // Advanced query with conditions
129
+ const result = await table
130
+ .query({
131
+ pk: "SPECIES#velociraptor",
132
+ sk: { operator: "begins_with", value: "PROFILE#" }
133
+ })
134
+ .where("type", "=", "DINOSAUR")
135
+ .whereGreaterThan("length", 6)
136
+ .limit(10)
137
+ .useIndex("GSI1")
138
+ .execute();
139
+
140
+ // Available query conditions:
141
+ // .where(field, operator, value) // Generic condition
142
+ // .whereEquals(field, value) // Equality check
143
+ // .whereBetween(field, start, end) // Range check
144
+ // .whereIn(field, values) // IN check
145
+ // .whereLessThan(field, value) // < check
146
+ // .whereLessThanOrEqual(field, value) // <= check
147
+ // .whereGreaterThan(field, value) // > check
148
+ // .whereGreaterThanOrEqual(field, value) // >= check
149
+ // .whereNotEqual(field, value) // <> check
150
+ // .whereBeginsWith(field, value) // begins_with check
151
+ // .whereContains(field, value) // contains check
152
+ // .whereNotContains(field, value) // not_contains check
153
+ // .whereExists(field) // attribute_exists check
154
+ // .whereNotExists(field) // attribute_not_exists check
155
+ // .whereAttributeType(field, type) // attribute_type check
156
+ ```
157
+
158
+ ### Scan Operations
159
+
160
+ ```ts
161
+ // Basic scan
162
+ const result = await table.scan().execute();
163
+
164
+ // Filtered scan
165
+ const result = await table
166
+ .scan()
167
+ .whereEquals("type", "DINOSAUR")
168
+ .where("length", ">", 20)
169
+ .limit(20)
170
+ .execute();
171
+
172
+ // Scan supports all the same conditions as Query operations
173
+ ```
174
+
175
+ ### Batch Operations
176
+
177
+ ```ts
178
+ // Batch write (put)
179
+ const dinos = [
180
+ { pk: "SPECIES#trex", sk: "PROFILE#001", name: "Rex", length: 40 },
181
+ { pk: "SPECIES#raptor", sk: "PROFILE#001", name: "Blue", length: 6 },
182
+ ];
183
+
184
+ await table.batchWrite(
185
+ dinos.map((dino) => ({ type: "put", item: dino }))
186
+ );
187
+
188
+ // Batch write (delete)
189
+ await table.batchWrite([
190
+ { type: "delete", key: { pk: "SPECIES#trex", sk: "PROFILE#001" } },
191
+ { type: "delete", key: { pk: "SPECIES#raptor", sk: "PROFILE#001" } },
192
+ ]);
193
+
194
+ // Batch operations automatically handle chunking for large datasets
195
+ ```
196
+
197
+ ### Pagination
198
+
199
+ ```ts
200
+ // Limit to 10 items per page
201
+ const paginator = await table.query({ pk: "SPECIES#trex" }).limit(10).paginate();
202
+ // const paginator = await table.scan().limit(10).paginate();
203
+
204
+ while (paginator.hasNextPage()) {
205
+ const page = await paginator.getPage();
206
+ console.log(page);
207
+ }
208
+ ```
209
+
210
+ ### Transaction Operations
211
+
212
+ Two ways to perform transactions:
213
+
214
+ #### Using withTransaction
215
+
216
+ ```ts
217
+ await table.withTransaction(async (trx) => {
218
+ table.put(trex).withTransaction(trx);
219
+ table.put(raptor).withTransaction(trx);
220
+ table.delete(brontoKey).withTransaction(trx);
221
+ });
222
+ ```
223
+
224
+ #### Using TransactionBuilder
225
+
226
+ ```ts
227
+ const transaction = new TransactionBuilder();
228
+
229
+ transaction
230
+ .addOperation({
231
+ put: { item: trex }
232
+ })
233
+ .addOperation({
234
+ put: { item: raptor }
235
+ })
236
+ .addOperation({
237
+ delete: { key: brontoKey }
238
+ });
239
+
240
+ await table.transactWrite(transaction);
241
+ ```
242
+
243
+ ## Repository Pattern
244
+
245
+ Create a repository by extending the `BaseRepository` class.
246
+
247
+ ```ts
248
+ import { BaseRepository } from "dyno-table";
249
+
250
+ type DinoRecord = {
251
+ id: string;
252
+ name: string;
253
+ diet: string;
254
+ length: number;
255
+ };
256
+
257
+ class DinoRepository extends BaseRepository<DinoRecord> {
258
+ protected createPrimaryKey(data: DinoRecord) {
259
+ return {
260
+ pk: `SPECIES#${data.id}`,
261
+ sk: `PROFILE#${data.id}`,
262
+ };
263
+ }
264
+
265
+ protected getType() {
266
+ return "DINOSAUR";
267
+ }
268
+
269
+ // Add custom methods
270
+ async findByDiet(diet: string) {
271
+ return this.scan()
272
+ .whereEquals("diet", diet)
273
+ .execute();
274
+ }
275
+
276
+ async findLargerThan(length: number) {
277
+ return this.scan()
278
+ .whereGreaterThan("length", length)
279
+ .execute();
280
+ }
281
+ }
282
+ ```
283
+
284
+ ### Repository Operations
285
+
286
+ The repository pattern in dyno-table not only provides a clean abstraction but also ensures data isolation through type-scoping. All operations available on the `Table` class are also available on your repository, but they're automatically scoped to the repository's type.
287
+
288
+ ```ts
289
+ const dinoRepo = new DinoRepository(table);
290
+
291
+ // Query all T-Rexes - automatically includes type="DINOSAUR" condition
292
+ const rexes = await dinoRepo
293
+ .query({ pk: "SPECIES#trex" })
294
+ .execute();
295
+
296
+ // Scan for large carnivores - automatically includes type="DINOSAUR"
297
+ const largeCarnivores = await dinoRepo
298
+ .scan()
299
+ .whereEquals("diet", "Carnivore")
300
+ .whereGreaterThan("length", 30)
301
+ .execute();
302
+
303
+ // Put operation, the type attribute is automatically along with the primary key/secondary key is created
304
+ await dinoRepo.create({
305
+ id: "trex",
306
+ name: "Rex",
307
+ diet: "Carnivore",
308
+ length: 40
309
+ }).execute();
310
+
311
+ // Update operation
312
+ await dinoRepo
313
+ .update({ pk: "SPECIES#trex", sk: "PROFILE#001" })
314
+ .set("diet", "Omnivore")
315
+ .execute();
316
+
317
+ // Delete operation
318
+ await dinoRepo
319
+ .delete({ pk: "SPECIES#trex", sk: "PROFILE#001" })
320
+ .execute();
321
+ ```
322
+
323
+ This type-scoping ensures that:
324
+ - Each repository only accesses its own data type
325
+ - Queries automatically include type filtering
326
+ - Put operations automatically include the type attribute
327
+ - Updates and deletes are constrained to the correct type
328
+
329
+ This pattern is particularly useful in single-table designs where multiple entity types share the same table. Each repository provides a type-safe, isolated view of its own data while preventing accidental cross-type operations.
330
+
331
+ ## Contributing 🤝
332
+ ```bash
333
+ # Installing the dependencies
334
+ pnpm i
335
+
336
+ # Installing the peerDependencies manually
337
+ pnpm i @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
338
+ ```
339
+
340
+ ### Developing
341
+
342
+ ```bash
343
+ docker run -p 8000:8000 amazon/dynamodb-local
344
+ ```
package/dist/index.d.ts CHANGED
@@ -1,6 +1,4 @@
1
- import * as _aws_sdk_lib_dynamodb from '@aws-sdk/lib-dynamodb';
2
1
  import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
3
- import { z } from 'zod';
4
2
 
5
3
  interface ExpressionAttributes {
6
4
  names?: Record<string, string>;
@@ -94,6 +92,13 @@ interface DynamoDeleteOperation {
94
92
  key: PrimaryKeyWithoutExpression;
95
93
  condition?: DynamoExpression;
96
94
  }
95
+ interface DynamoScanOperation {
96
+ type: "scan";
97
+ filter?: DynamoExpression;
98
+ limit?: number;
99
+ pageKey?: Record<string, unknown>;
100
+ indexName?: string;
101
+ }
97
102
  interface DynamoBatchWriteOperation {
98
103
  type: "batchWrite";
99
104
  operations: DynamoBatchWriteItem[];
@@ -116,7 +121,7 @@ interface DynamoTransactOperation {
116
121
  };
117
122
  }>;
118
123
  }
119
- type DynamoOperation = DynamoPutOperation | DynamoUpdateOperation | DynamoQueryOperation | DynamoDeleteOperation | DynamoBatchWriteOperation | DynamoTransactOperation;
124
+ type DynamoOperation = DynamoPutOperation | DynamoUpdateOperation | DynamoQueryOperation | DynamoDeleteOperation | DynamoBatchWriteOperation | DynamoTransactOperation | DynamoScanOperation;
120
125
  type PrimaryKeyWithoutExpression = {
121
126
  pk: string;
122
127
  sk?: string;
@@ -129,72 +134,121 @@ type BatchWriteOperation = {
129
134
  key: PrimaryKeyWithoutExpression;
130
135
  };
131
136
 
132
- declare abstract class OperationBuilder<T extends DynamoOperation> {
137
+ interface DynamoRecord {
138
+ [key: string]: unknown;
139
+ }
140
+
141
+ type StringKeys<T> = Extract<keyof T, string>;
142
+ /**
143
+ * Base builder class for DynamoDB operations that supports condition expressions
144
+ * @see https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html
145
+ */
146
+ declare abstract class OperationBuilder<T extends DynamoRecord, TOperation extends DynamoOperation> {
133
147
  protected expressionBuilder: IExpressionBuilder;
134
148
  protected conditions: Array<{
135
- field: string;
149
+ field: keyof T;
136
150
  operator: ConditionOperator;
137
151
  value?: unknown;
138
152
  }>;
139
153
  constructor(expressionBuilder: IExpressionBuilder);
140
- where(field: string, operator: FilterOperator, value: unknown): this;
141
- whereExists(field: string): this;
142
- whereNotExists(field: string): this;
143
- whereEquals(field: string, value: unknown): this;
144
- whereBetween(field: string, start: unknown, end: unknown): this;
145
- whereIn(field: string, values: unknown[]): this;
154
+ where<K extends keyof T>(field: K, operator: FilterOperator, value: T[K] | T[K][]): this;
155
+ whereExists<K extends StringKeys<T>>(field: K): this;
156
+ whereNotExists<K extends keyof T>(field: K): this;
157
+ whereEquals<K extends keyof T>(field: K, value: T[K]): this;
158
+ whereBetween<K extends keyof T>(field: K, start: T[K], end: T[K]): this;
159
+ whereIn<K extends keyof T>(field: K, values: T[K][]): this;
160
+ whereLessThan<K extends keyof T>(field: K, value: T[K]): this;
161
+ whereLessThanOrEqual<K extends keyof T>(field: K, value: T[K]): this;
162
+ whereGreaterThan<K extends keyof T>(field: K, value: T[K]): this;
163
+ whereGreaterThanOrEqual<K extends keyof T>(field: K, value: T[K]): this;
164
+ whereNotEqual<K extends keyof T>(field: K, value: T[K]): this;
165
+ whereBeginsWith<K extends keyof T>(field: K, value: T[K]): this;
166
+ whereContains<K extends keyof T>(field: K, value: T[K]): this;
167
+ whereNotContains<K extends keyof T>(field: K, value: T[K]): this;
168
+ whereAttributeType<K extends keyof T>(field: K, value: "S" | "SS" | "N" | "NS" | "B" | "BS" | "BOOL" | "NULL" | "M" | "L"): this;
169
+ whereSize<K extends keyof T>(field: K, value: T[K]): this;
146
170
  protected buildConditionExpression(): ExpressionResult;
147
- abstract build(): T;
171
+ abstract build(): TOperation;
148
172
  }
149
173
 
150
- declare class PutBuilder extends OperationBuilder<DynamoPutOperation> {
151
- private readonly item;
174
+ declare class PutBuilder<T extends DynamoRecord> extends OperationBuilder<T, DynamoPutOperation> {
152
175
  private readonly onBuild;
153
- constructor(item: Record<string, unknown>, expressionBuilder: IExpressionBuilder, onBuild: (operation: DynamoPutOperation) => Promise<void>);
176
+ private item;
177
+ constructor(item: T, expressionBuilder: IExpressionBuilder, onBuild: (operation: DynamoPutOperation) => Promise<T>);
178
+ set<K extends keyof T>(field: K, value: T[K]): this;
179
+ setMany(attributes: Partial<T>): this;
154
180
  build(): DynamoPutOperation;
155
- execute(): Promise<void>;
181
+ /**
182
+ * Runs the put operation to insert the provided attributes into the table.
183
+ *
184
+ * @returns The provided attributes. This does not load the model from the DB after insert
185
+ */
186
+ execute(): Promise<T>;
187
+ }
188
+
189
+ interface DynamoQueryResponse {
190
+ Items?: Record<string, unknown>[];
191
+ Count?: number;
192
+ ScannedCount?: number;
193
+ LastEvaluatedKey?: Record<string, unknown>;
156
194
  }
157
195
 
158
- declare class QueryBuilder extends OperationBuilder<DynamoQueryOperation> {
196
+ declare class QueryBuilder<T extends DynamoRecord> extends OperationBuilder<T, DynamoQueryOperation> {
159
197
  private readonly key;
160
198
  private readonly indexConfig;
161
199
  private readonly onBuild;
162
200
  private limitValue?;
163
201
  private indexNameValue?;
164
- constructor(key: PrimaryKey, indexConfig: TableIndexConfig, expressionBuilder: IExpressionBuilder, onBuild: (operation: DynamoQueryOperation) => Promise<{
165
- Items?: Record<string, unknown>[];
166
- Count?: number;
167
- ScannedCount?: number;
168
- LastEvaluatedKey?: Record<string, unknown>;
169
- }>);
202
+ constructor(key: PrimaryKey, indexConfig: TableIndexConfig, expressionBuilder: IExpressionBuilder, onBuild: (operation: DynamoQueryOperation) => Promise<DynamoQueryResponse>);
170
203
  limit(value: number): this;
171
204
  useIndex(indexName: string): this;
172
205
  build(): DynamoQueryOperation;
173
- execute(): Promise<{
174
- Items?: Record<string, unknown>[];
175
- Count?: number;
176
- ScannedCount?: number;
177
- LastEvaluatedKey?: Record<string, unknown>;
178
- }>;
206
+ execute(): Promise<DynamoQueryResponse>;
179
207
  }
180
208
 
181
- declare class UpdateBuilder extends OperationBuilder<DynamoUpdateOperation> {
209
+ declare class UpdateBuilder<T extends DynamoRecord> extends OperationBuilder<T, DynamoUpdateOperation> {
182
210
  private readonly key;
183
211
  private readonly onBuild;
184
212
  private updates;
185
213
  constructor(key: PrimaryKeyWithoutExpression, expressionBuilder: IExpressionBuilder, onBuild: (operation: DynamoUpdateOperation) => Promise<{
186
- Attributes?: Record<string, unknown>;
214
+ Attributes?: T;
187
215
  }>);
188
- set(field: string, value: unknown): this;
189
- setMany(attribtues: Record<string, unknown>): this;
190
- remove(...fields: string[]): this;
191
- increment(field: string, by?: number): this;
216
+ set<K extends keyof T>(field: K, value: T[K]): this;
217
+ setMany(attributes: Partial<T>): this;
218
+ remove(...fields: Array<keyof T>): this;
219
+ increment<K extends keyof T>(field: K, by?: number): this;
192
220
  build(): DynamoUpdateOperation;
193
221
  execute(): Promise<{
194
- Attributes?: Record<string, unknown>;
222
+ Attributes?: T;
195
223
  }>;
196
224
  }
197
225
 
226
+ declare class ScanBuilder<T extends DynamoRecord> extends OperationBuilder<T, DynamoScanOperation> {
227
+ private readonly onBuild;
228
+ private limitValue?;
229
+ private indexNameValue?;
230
+ private pageKeyValue?;
231
+ constructor(expressionBuilder: IExpressionBuilder, onBuild: (operation: DynamoScanOperation) => Promise<DynamoQueryResponse>);
232
+ limit(value: number): this;
233
+ useIndex(indexName: string): this;
234
+ startKey(key: Record<string, unknown>): this;
235
+ build(): {
236
+ type: "scan";
237
+ filter: {
238
+ expression: string;
239
+ names: Record<string, string> | undefined;
240
+ values: Record<string, unknown> | undefined;
241
+ } | undefined;
242
+ limit: number | undefined;
243
+ pageKey: Record<string, unknown> | undefined;
244
+ indexName: string | undefined;
245
+ };
246
+ execute(): Promise<DynamoQueryResponse>;
247
+ }
248
+
249
+ type IndexConfig = Record<string, TableIndexConfig> & {
250
+ primary: TableIndexConfig;
251
+ };
198
252
  declare class Table {
199
253
  private readonly dynamoService;
200
254
  private readonly expressionBuilder;
@@ -202,22 +256,18 @@ declare class Table {
202
256
  constructor({ client, tableName, tableIndexes, expressionBuilder, }: {
203
257
  client: DynamoDBDocument;
204
258
  tableName: string;
205
- tableIndexes: Record<string, TableIndexConfig>;
259
+ tableIndexes: IndexConfig;
206
260
  expressionBuilder?: ExpressionBuilder;
207
261
  });
208
262
  getIndexConfig(indexName?: string): TableIndexConfig;
209
- put(item: Record<string, unknown>): PutBuilder;
210
- update(key: PrimaryKeyWithoutExpression, data?: Record<string, unknown>): UpdateBuilder;
211
- query(key: PrimaryKey): QueryBuilder;
263
+ put<T extends DynamoRecord>(item: T): PutBuilder<T>;
264
+ update<T extends DynamoRecord>(key: PrimaryKeyWithoutExpression, data?: Partial<T>): UpdateBuilder<T>;
265
+ query<T extends DynamoRecord>(key: PrimaryKey): QueryBuilder<T>;
212
266
  get(key: PrimaryKeyWithoutExpression, options?: {
213
267
  indexName?: string;
214
268
  }): Promise<Record<string, any> | undefined>;
215
269
  delete(key: PrimaryKeyWithoutExpression): Promise<unknown>;
216
- scan(filters?: FilterCondition[], options?: {
217
- limit?: number;
218
- pageKey?: Record<string, unknown>;
219
- indexName?: string;
220
- }): Promise<_aws_sdk_lib_dynamodb.ScanCommandOutput>;
270
+ scan<T extends DynamoRecord>(): ScanBuilder<T>;
221
271
  batchWrite(operations: BatchWriteOperation[]): Promise<unknown>;
222
272
  transactWrite(operations: Array<{
223
273
  put?: {
@@ -255,29 +305,100 @@ declare class Table {
255
305
  private validateKey;
256
306
  }
257
307
 
258
- type InferZodSchema<T extends z.ZodType> = z.infer<T>;
259
- declare abstract class BaseRepository<TSchema extends z.ZodType> {
308
+ declare abstract class BaseRepository<TData extends DynamoRecord> {
260
309
  protected readonly table: Table;
261
- protected readonly schema: TSchema;
262
- constructor(table: Table, schema: TSchema);
263
- protected abstract createPrimaryKey(data: InferZodSchema<TSchema>): PrimaryKeyWithoutExpression;
264
- protected abstract getIndexKeys(): {
265
- pk: string;
266
- sk?: string;
267
- };
310
+ constructor(table: Table);
311
+ /**
312
+ * Templates out the primary key for the record, it is consumed for create, put, update and delete actions
313
+ *
314
+ * Unfortunately, TypeScript is not inferring the TData type when implmenting this method in a subclass.
315
+ * https://github.com/microsoft/TypeScript/issues/32082
316
+ */
317
+ protected abstract createPrimaryKey(data: TData): PrimaryKeyWithoutExpression;
268
318
  /**
269
319
  * Default attribute applied to ALL records that get stored in DDB
320
+ * Defines the type of the record.
321
+ * For example User, Post, Comment etc.
322
+ *
323
+ * This helps to ensure isolation of models when using a single table design
324
+ * @returns The type of the record as a string.
270
325
  */
271
326
  protected abstract getType(): string;
327
+ /**
328
+ * Used to define the attribute name for the type.
329
+ * @returns The type attribute name as a string.
330
+ */
272
331
  protected abstract getTypeAttributeName(): string;
273
- protected beforeInsert(data: InferZodSchema<TSchema>): InferZodSchema<TSchema>;
274
- protected beforeUpdate(data: InferZodSchema<TSchema>): InferZodSchema<TSchema>;
275
- create(data: InferZodSchema<TSchema>): Promise<InferZodSchema<TSchema>>;
276
- update(key: PrimaryKeyWithoutExpression, updates: Partial<InferZodSchema<TSchema>>): Promise<InferZodSchema<TSchema>>;
277
- delete(key: PrimaryKeyWithoutExpression): Promise<void>;
278
- findOne(key: PrimaryKeyWithoutExpression): Promise<InferZodSchema<TSchema> | null>;
279
- findOrFail(key: PrimaryKeyWithoutExpression): Promise<InferZodSchema<TSchema>>;
280
- protected query(key: PrimaryKeyWithoutExpression): QueryBuilder;
332
+ /**
333
+ * Hook method called before inserting a record.
334
+ * Subclasses can override this method to modify the data before insertion.
335
+ * @param data - The record data.
336
+ * @returns The modified record data.
337
+ */
338
+ protected beforeInsert(data: TData): TData;
339
+ /**
340
+ * Hook method called before updating a record.
341
+ * Subclasses can override this method to modify the data before updating.
342
+ * @param data - The partial record data to be updated.
343
+ * @returns The modified partial record data.
344
+ */
345
+ protected beforeUpdate(data: Partial<TData>): Partial<TData>;
346
+ /**
347
+ * Checks if a record exists in the table.
348
+ * @param key - The primary key of the record.
349
+ * @returns A promise that resolves to true if the record exists, false otherwise.
350
+ */
351
+ exists(key: PrimaryKeyWithoutExpression): Promise<boolean>;
352
+ /**
353
+ * Type guard to check if the value is a primary key.
354
+ * @param value - The value to check.
355
+ * @returns True if the value is a primary key, false otherwise.
356
+ */
357
+ protected isPrimaryKey(value: PrimaryKeyWithoutExpression | TData): value is PrimaryKeyWithoutExpression;
358
+ /**
359
+ * Creates a new record in the table.
360
+ * @param data - The record data.
361
+ * @returns A PutBuilder instance to execute the put operation.
362
+ */
363
+ create(data: TData): PutBuilder<TData>;
364
+ /**
365
+ * Updates an existing record in the table.
366
+ * @param key - The primary key of the record.
367
+ * @param updates - The partial record data to be updated.
368
+ * @returns A promise that resolves to the updated record or null if the record does not exist.
369
+ */
370
+ update(key: PrimaryKeyWithoutExpression, updates: Partial<TData>): Promise<TData | null>;
371
+ /**
372
+ * Upserts (inserts or updates) a record in the table.
373
+ * @param data - The record data.
374
+ * @returns A PutBuilder instance to execute the put operation.
375
+ */
376
+ upsert(data: TData): PutBuilder<TData>;
377
+ /**
378
+ * Deletes a record from the table.
379
+ * @param keyOrDTO - The primary key or the record data.
380
+ * @returns A promise that resolves when the record is deleted.
381
+ */
382
+ delete(keyOrDTO: PrimaryKeyWithoutExpression | TData): Promise<void>;
383
+ /**
384
+ * Finds a single record by its primary key.
385
+ * @param key - The primary key of the record.
386
+ * @returns A promise that resolves to the record or null if the record does not exist.
387
+ */
388
+ findOne(key: PrimaryKey): Promise<TData | null>;
389
+ /**
390
+ * Finds a single record by its primary key or throws an error if the record does not exist.
391
+ * @param key - The primary key of the record.
392
+ * @returns A promise that resolves to the record.
393
+ * @throws An error if the record does not exist.
394
+ */
395
+ findOrFail(key: PrimaryKeyWithoutExpression): Promise<TData>;
396
+ /**
397
+ * Creates a query builder for querying records by their primary key.
398
+ * @param key - The primary key of the record.
399
+ * @returns A QueryBuilder instance to build and execute the query.
400
+ */
401
+ query(key: PrimaryKey): QueryBuilder<TData>;
281
402
  }
282
403
 
283
404
  interface RetryStrategy {