@ftschopp/dynatable-core 1.4.2 → 1.4.4

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/builders/query/create-query-builder.d.ts.map +1 -1
  3. package/dist/builders/query/create-query-builder.js +19 -2
  4. package/dist/builders/update/create-update-builder.d.ts.map +1 -1
  5. package/dist/builders/update/create-update-builder.js +25 -0
  6. package/dist/core/types.d.ts +5 -0
  7. package/dist/core/types.d.ts.map +1 -1
  8. package/dist/utils/model-utils.d.ts +9 -3
  9. package/dist/utils/model-utils.d.ts.map +1 -1
  10. package/dist/utils/model-utils.js +15 -7
  11. package/package.json +19 -2
  12. package/eslint.config.mjs +0 -4
  13. package/jest.config.js +0 -11
  14. package/src/builders/README.md +0 -339
  15. package/src/builders/batch-get/README.md +0 -43
  16. package/src/builders/batch-get/create-batch-get-builder.test.ts +0 -193
  17. package/src/builders/batch-get/create-batch-get-builder.ts +0 -108
  18. package/src/builders/batch-get/index.ts +0 -2
  19. package/src/builders/batch-get/types.ts +0 -28
  20. package/src/builders/batch-write/README.md +0 -204
  21. package/src/builders/batch-write/create-batch-write-builder.test.ts +0 -173
  22. package/src/builders/batch-write/create-batch-write-builder.ts +0 -49
  23. package/src/builders/batch-write/index.ts +0 -2
  24. package/src/builders/batch-write/types.ts +0 -33
  25. package/src/builders/delete/create-delete-builder.test.ts +0 -294
  26. package/src/builders/delete/create-delete-builder.ts +0 -100
  27. package/src/builders/delete/index.ts +0 -2
  28. package/src/builders/delete/types.ts +0 -17
  29. package/src/builders/get/create-get-builder.test.ts +0 -272
  30. package/src/builders/get/create-get-builder.ts +0 -117
  31. package/src/builders/get/index.ts +0 -2
  32. package/src/builders/get/types.ts +0 -39
  33. package/src/builders/index.ts +0 -14
  34. package/src/builders/put/create-put-builder.test.ts +0 -213
  35. package/src/builders/put/create-put-builder.ts +0 -160
  36. package/src/builders/put/index.ts +0 -2
  37. package/src/builders/put/types.ts +0 -24
  38. package/src/builders/query/create-query-builder.test.ts +0 -770
  39. package/src/builders/query/create-query-builder.ts +0 -455
  40. package/src/builders/query/index.ts +0 -2
  41. package/src/builders/query/types.ts +0 -109
  42. package/src/builders/scan/create-scan-builder.test.ts +0 -416
  43. package/src/builders/scan/create-scan-builder.ts +0 -265
  44. package/src/builders/scan/index.ts +0 -2
  45. package/src/builders/scan/types.ts +0 -88
  46. package/src/builders/shared/conditions.ts +0 -58
  47. package/src/builders/shared/index.ts +0 -4
  48. package/src/builders/shared/operators.test.ts +0 -270
  49. package/src/builders/shared/operators.ts +0 -208
  50. package/src/builders/shared/projection.ts +0 -28
  51. package/src/builders/shared/types.ts +0 -101
  52. package/src/builders/transact-get/README.md +0 -167
  53. package/src/builders/transact-get/create-transact-get-builder.test.ts +0 -239
  54. package/src/builders/transact-get/create-transact-get-builder.ts +0 -67
  55. package/src/builders/transact-get/index.ts +0 -2
  56. package/src/builders/transact-get/types.ts +0 -31
  57. package/src/builders/transact-write/README.md +0 -202
  58. package/src/builders/transact-write/create-transact-write-builder.test.ts +0 -288
  59. package/src/builders/transact-write/create-transact-write-builder.ts +0 -124
  60. package/src/builders/transact-write/index.ts +0 -2
  61. package/src/builders/transact-write/types.ts +0 -71
  62. package/src/builders/update/create-update-builder.test.ts +0 -573
  63. package/src/builders/update/create-update-builder.ts +0 -375
  64. package/src/builders/update/index.ts +0 -2
  65. package/src/builders/update/types.ts +0 -63
  66. package/src/core/types.test.ts +0 -641
  67. package/src/core/types.ts +0 -366
  68. package/src/entity/create-entity-api.ts +0 -224
  69. package/src/entity/index.ts +0 -19
  70. package/src/entity/middleware/factories.ts +0 -15
  71. package/src/entity/middleware/types.ts +0 -7
  72. package/src/entity/middleware/with-middleware.ts +0 -37
  73. package/src/entity/types.ts +0 -79
  74. package/src/entity/validation/key-validation.ts +0 -34
  75. package/src/index.ts +0 -23
  76. package/src/table.ts +0 -116
  77. package/src/utils/dynamodb-logger.test.ts +0 -246
  78. package/src/utils/dynamodb-logger.ts +0 -175
  79. package/src/utils/model-utils.test.ts +0 -452
  80. package/src/utils/model-utils.ts +0 -188
  81. package/src/utils/zod-utils.test.ts +0 -363
  82. package/src/utils/zod-utils.ts +0 -56
  83. package/tests/integration/instagram-clone.integration.test.ts +0 -1019
  84. package/tests/integration/pagination-timestamps.integration.test.ts +0 -374
  85. package/tests/integration/transactions.integration.test.ts +0 -528
  86. package/tsconfig.json +0 -12
@@ -1,79 +0,0 @@
1
- import { DynamoDBLogger } from '@/utils/dynamodb-logger';
2
- import {
3
- GetBuilder,
4
- PutBuilder,
5
- QueryBuilder,
6
- UpdateBuilder,
7
- DeleteBuilder,
8
- ScanBuilder,
9
- BatchGetBuilder,
10
- BatchWriteBuilder,
11
- } from '@/builders';
12
-
13
- /**
14
- * Options for creating the Entity API
15
- */
16
- export type EntityAPIOptions = {
17
- logger?: DynamoDBLogger;
18
- timestamps?: boolean;
19
- cleanInternalKeys?: boolean;
20
- };
21
-
22
- /**
23
- * Entity API interface for a model
24
- */
25
- export type EntityAPI<Model, Input, KeyInput> = {
26
- /**
27
- * Retrieves an item by its key.
28
- * @param key - Partial or full key object to identify the item
29
- * @returns GetBuilder configured for the item
30
- */
31
- get: (key: KeyInput) => GetBuilder<KeyInput, Model>;
32
-
33
- /**
34
- * Puts an item into the table after validation and applying defaults.
35
- * @param item - The input data to put
36
- * @returns PutBuilder configured for the item
37
- */
38
- put: (item: Input) => PutBuilder<Model>;
39
-
40
- /**
41
- * Queries items using key conditions.
42
- * @returns QueryBuilder for building and executing the query
43
- */
44
- query: () => QueryBuilder<Model>;
45
-
46
- /**
47
- * Scans the entire table or index without key conditions.
48
- * @returns ScanBuilder for building and executing the scan
49
- */
50
- scan: () => ScanBuilder<Model>;
51
-
52
- /**
53
- * Updates an item by its key.
54
- * @param key - Partial or full key object to identify the item
55
- * @returns UpdateBuilder configured for the item
56
- */
57
- update: (key: KeyInput) => UpdateBuilder<Model>;
58
-
59
- /**
60
- * Deletes an item by its key.
61
- * @param key - Partial or full key object to identify the item
62
- * @returns DeleteBuilder configured for the item
63
- */
64
- delete: (key: KeyInput) => DeleteBuilder<Model>;
65
-
66
- /**
67
- * Retrieves multiple items by their keys in a single batch operation.
68
- * @param keys - Array of key objects to retrieve
69
- * @returns BatchGetBuilder configured for the items
70
- */
71
- batchGet: (keys: KeyInput[]) => BatchGetBuilder<Model>;
72
-
73
- /**
74
- * Writes multiple items in a single batch operation (puts or deletes).
75
- * @param items - Array of items to put
76
- * @returns BatchWriteBuilder configured for the items
77
- */
78
- batchWrite: (items: Input[]) => BatchWriteBuilder;
79
- };
@@ -1,34 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
-
3
- import { ModelDefinition } from '@/core/types';
4
- import { extractTemplateVars } from '@/utils/model-utils';
5
-
6
- /**
7
- * Validates that all required key fields are present in the key object
8
- * @throws Error if any required key fields are missing
9
- */
10
- export function validateKeyFields(
11
- modelName: string,
12
- model: ModelDefinition,
13
- key: Record<string, unknown>,
14
- operation?: string
15
- ): void {
16
- // Extract required fields from key templates
17
- const requiredFields = new Set<string>();
18
- if (model.key) {
19
- for (const keyDef of Object.values(model.key)) {
20
- extractTemplateVars(keyDef.value).forEach((field) => requiredFields.add(field));
21
- }
22
- }
23
-
24
- // Check if all required fields are present
25
- const missingFields = Array.from(requiredFields).filter((field) => key[field] === undefined);
26
-
27
- if (missingFields.length > 0) {
28
- const operationSuffix = operation ? ` for ${operation}` : '';
29
- throw new Error(
30
- `[${modelName}] Missing required key field(s)${operationSuffix}: ${missingFields.join(', ')}. ` +
31
- `Required fields: ${Array.from(requiredFields).join(', ')}`
32
- );
33
- }
34
- }
package/src/index.ts DELETED
@@ -1,23 +0,0 @@
1
- export * from './table';
2
- export {
3
- type SchemaDefinition,
4
- type ModelDefinition,
5
- type PrimaryKeyDefinition,
6
- type KeyDefinition,
7
- type AttributeDefinition,
8
- type IndexDefinition,
9
- type IndexesDefinition,
10
- type SchemaParams,
11
- type InferInput,
12
- type InferModel,
13
- type InferKeyInput,
14
- type InferModelFromSchema,
15
- type InferInputFromSchema,
16
- type TimestampFields,
17
- type ArrayItem,
18
- } from './core/types';
19
- export {
20
- createDynamoDBLogger,
21
- type DynamoDBLogger,
22
- type DynamoDBLoggerConfig,
23
- } from './utils/dynamodb-logger';
package/src/table.ts DELETED
@@ -1,116 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
3
- import {
4
- InferKeyInput,
5
- InferModelFromSchema,
6
- InferInputFromSchema,
7
- SchemaDefinition,
8
- } from './core/types';
9
- import { createEntityAPI, EntityAPI } from './entity';
10
- import { createTransactWriteBuilder, TransactWriteBuilder } from './builders/transact-write';
11
- import { createTransactGetBuilder, TransactGetBuilder } from './builders/transact-get';
12
- import { DynamoDBLogger } from './utils/dynamodb-logger';
13
-
14
- /**
15
- * Configuration options for the Table instance
16
- */
17
- export type TableConfig<S extends SchemaDefinition> = {
18
- /** The DynamoDB table name */
19
- name: string;
20
- /** AWS DynamoDB client instance */
21
- client: DynamoDBClient;
22
- /** Optional logger instance for DynamoDB operations */
23
- logger?: DynamoDBLogger;
24
- /** The schema definition for all models in the table */
25
- schema: S;
26
- };
27
-
28
- /**
29
- * Helper type to force TypeScript to expand/simplify a type.
30
- * This helps with type inference in callbacks by making the type "concrete".
31
- */
32
- type Simplify<T> = { [K in keyof T]: T[K] } & {};
33
-
34
- /**
35
- * Internal helper type to infer all entity APIs from schema definition
36
- */
37
- type EntityMap<S extends SchemaDefinition> = {
38
- [K in keyof S['models']]: EntityAPI<
39
- Simplify<InferModelFromSchema<S, K>>,
40
- Simplify<InferInputFromSchema<S, K>>,
41
- Simplify<InferKeyInput<S['models'][K]>>
42
- >;
43
- };
44
-
45
- /**
46
- * Represents a typed DynamoDB Table with entity APIs
47
- *
48
- * Provides access to all entity operations (get, put, delete, etc.)
49
- * via `table.entities.<EntityName>`.
50
- */
51
- export class Table<S extends SchemaDefinition> {
52
- /** Generated entity APIs */
53
- public readonly entities: EntityMap<S>;
54
-
55
- /** DynamoDB client */
56
- private readonly client: DynamoDBClient;
57
-
58
- constructor(config: TableConfig<S>) {
59
- const { client, schema, logger, name: tableName } = config;
60
-
61
- this.client = client;
62
-
63
- const rawEntities: Record<string, any> = {};
64
-
65
- for (const modelName in schema.models) {
66
- const model = schema.models[modelName];
67
- if (!model) {
68
- throw new Error(`Model '${modelName}' is missing in schema`);
69
- }
70
-
71
- rawEntities[modelName] = createEntityAPI(tableName, modelName, model, client, {
72
- logger,
73
- timestamps: schema.params?.timestamps ?? false,
74
- cleanInternalKeys: schema.params?.cleanInternalKeys ?? false,
75
- });
76
- }
77
-
78
- this.entities = rawEntities as EntityMap<S>;
79
- }
80
-
81
- /**
82
- * Creates a new TransactWrite builder for atomic multi-item write operations
83
- *
84
- * @returns A TransactWriteBuilder instance
85
- *
86
- * @example
87
- * ```typescript
88
- * // Like a photo atomically
89
- * await table.transactWrite()
90
- * .addPut(table.entities.Like.put({ photoId: "123", likingUsername: "alice" }).dbParams())
91
- * .addUpdate(table.entities.Photo.update({ username: "bob", photoId: "123" }).add("likesCount", 1).dbParams())
92
- * .execute();
93
- * ```
94
- */
95
- transactWrite(): TransactWriteBuilder {
96
- return createTransactWriteBuilder(this.client);
97
- }
98
-
99
- /**
100
- * Creates a new TransactGet builder for atomic multi-item read operations
101
- *
102
- * @returns A TransactGetBuilder instance
103
- *
104
- * @example
105
- * ```typescript
106
- * // Get user and photo atomically
107
- * const [user, photo] = await table.transactGet()
108
- * .addGet(table.entities.User.get({ username: "alice" }).dbParams())
109
- * .addGet(table.entities.Photo.get({ username: "alice", photoId: "123" }).dbParams())
110
- * .execute();
111
- * ```
112
- */
113
- transactGet(): TransactGetBuilder {
114
- return createTransactGetBuilder(this.client);
115
- }
116
- }
@@ -1,246 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { createDynamoDBLogger } from './dynamodb-logger';
3
-
4
- describe('DynamoDB Logger', () => {
5
- describe('createDynamoDBLogger', () => {
6
- it('should not log when disabled', () => {
7
- const mockLoggerFn = jest.fn();
8
- const logger = createDynamoDBLogger({
9
- enabled: false,
10
- logParams: true,
11
- logResponse: true,
12
- loggerFn: mockLoggerFn,
13
- });
14
-
15
- logger.log('GetCommand', { TableName: 'Users' }, { Item: { id: '123' } });
16
-
17
- expect(mockLoggerFn).not.toHaveBeenCalled();
18
- });
19
-
20
- it('should log when enabled', () => {
21
- const mockLoggerFn = jest.fn();
22
- const logger = createDynamoDBLogger({
23
- enabled: true,
24
- logParams: true,
25
- logResponse: false,
26
- loggerFn: mockLoggerFn,
27
- });
28
-
29
- logger.log('GetCommand', { TableName: 'Users' });
30
-
31
- expect(mockLoggerFn).toHaveBeenCalledTimes(1);
32
- const loggedMessage = mockLoggerFn.mock.calls[0][0];
33
- expect(loggedMessage).toContain('[DynamoDB] GetCommand');
34
- expect(loggedMessage).toContain('šŸ“¤ Request Parameters:');
35
- expect(loggedMessage).toContain('"TableName": "Users"');
36
- });
37
-
38
- it('should log both params and response when configured', () => {
39
- const mockLoggerFn = jest.fn();
40
- const logger = createDynamoDBLogger({
41
- enabled: true,
42
- logParams: true,
43
- logResponse: true,
44
- loggerFn: mockLoggerFn,
45
- });
46
-
47
- const params = { TableName: 'Users', Key: { PK: 'USER#123' } };
48
- const response = { Item: { PK: 'USER#123', username: 'johndoe' } };
49
-
50
- logger.log('GetCommand', params, response);
51
-
52
- expect(mockLoggerFn).toHaveBeenCalledTimes(1);
53
- const loggedMessage = mockLoggerFn.mock.calls[0][0];
54
-
55
- expect(loggedMessage).toContain('[DynamoDB] GetCommand');
56
- expect(loggedMessage).toContain('šŸ“¤ Request Parameters:');
57
- expect(loggedMessage).toContain('"TableName": "Users"');
58
- expect(loggedMessage).toContain('šŸ“„ Response:');
59
- expect(loggedMessage).toContain('"username": "johndoe"');
60
- });
61
-
62
- it('should log only response when logParams is false', () => {
63
- const mockLoggerFn = jest.fn();
64
- const logger = createDynamoDBLogger({
65
- enabled: true,
66
- logParams: false,
67
- logResponse: true,
68
- loggerFn: mockLoggerFn,
69
- });
70
-
71
- const params = { TableName: 'Users' };
72
- const response = { Item: { id: '123' } };
73
-
74
- logger.log('GetCommand', params, response);
75
-
76
- expect(mockLoggerFn).toHaveBeenCalledTimes(1);
77
- const loggedMessage = mockLoggerFn.mock.calls[0][0];
78
-
79
- expect(loggedMessage).toContain('[DynamoDB] GetCommand');
80
- expect(loggedMessage).not.toContain('šŸ“¤ Request Parameters:');
81
- expect(loggedMessage).toContain('šŸ“„ Response:');
82
- });
83
-
84
- it('should log only params when logResponse is false', () => {
85
- const mockLoggerFn = jest.fn();
86
- const logger = createDynamoDBLogger({
87
- enabled: true,
88
- logParams: true,
89
- logResponse: false,
90
- loggerFn: mockLoggerFn,
91
- });
92
-
93
- const params = { TableName: 'Users' };
94
- const response = { Item: { id: '123' } };
95
-
96
- logger.log('GetCommand', params, response);
97
-
98
- expect(mockLoggerFn).toHaveBeenCalledTimes(1);
99
- const loggedMessage = mockLoggerFn.mock.calls[0][0];
100
-
101
- expect(loggedMessage).toContain('[DynamoDB] GetCommand');
102
- expect(loggedMessage).toContain('šŸ“¤ Request Parameters:');
103
- expect(loggedMessage).not.toContain('šŸ“„ Response:');
104
- });
105
-
106
- it('should use console.log by default when no loggerFn is provided', () => {
107
- const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
108
-
109
- const logger = createDynamoDBLogger({
110
- enabled: true,
111
- logParams: true,
112
- logResponse: false,
113
- });
114
-
115
- logger.log('GetCommand', { TableName: 'Users' });
116
-
117
- expect(consoleLogSpy).toHaveBeenCalledTimes(1);
118
-
119
- consoleLogSpy.mockRestore();
120
- });
121
-
122
- it('should include operation name and timestamp in log', () => {
123
- const mockLoggerFn = jest.fn();
124
- const logger = createDynamoDBLogger({
125
- enabled: true,
126
- logParams: true,
127
- logResponse: false,
128
- loggerFn: mockLoggerFn,
129
- });
130
-
131
- logger.log('QueryCommand', { TableName: 'Posts' });
132
-
133
- expect(mockLoggerFn).toHaveBeenCalledTimes(1);
134
- const loggedMessage = mockLoggerFn.mock.calls[0][0];
135
-
136
- expect(loggedMessage).toContain('[DynamoDB] QueryCommand');
137
- // Timestamp should be in ISO format
138
- expect(loggedMessage).toMatch(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
139
- });
140
-
141
- it('should format log with proper separators', () => {
142
- const mockLoggerFn = jest.fn();
143
- const logger = createDynamoDBLogger({
144
- enabled: true,
145
- logParams: true,
146
- logResponse: false,
147
- loggerFn: mockLoggerFn,
148
- });
149
-
150
- logger.log('GetCommand', { TableName: 'Users' });
151
-
152
- const loggedMessage = mockLoggerFn.mock.calls[0][0];
153
-
154
- // Should have separators (80 equal signs)
155
- expect(loggedMessage).toContain('='.repeat(80));
156
- });
157
-
158
- it('should handle undefined response gracefully', () => {
159
- const mockLoggerFn = jest.fn();
160
- const logger = createDynamoDBLogger({
161
- enabled: true,
162
- logParams: true,
163
- logResponse: true,
164
- loggerFn: mockLoggerFn,
165
- });
166
-
167
- logger.log('GetCommand', { TableName: 'Users' }, undefined);
168
-
169
- expect(mockLoggerFn).toHaveBeenCalledTimes(1);
170
- const loggedMessage = mockLoggerFn.mock.calls[0][0];
171
-
172
- expect(loggedMessage).toContain('šŸ“¤ Request Parameters:');
173
- expect(loggedMessage).not.toContain('šŸ“„ Response:');
174
- });
175
-
176
- it('should work with different operation types', () => {
177
- const mockLoggerFn = jest.fn();
178
- const logger = createDynamoDBLogger({
179
- enabled: true,
180
- logParams: true,
181
- logResponse: false,
182
- loggerFn: mockLoggerFn,
183
- });
184
-
185
- const operations = [
186
- 'GetCommand',
187
- 'PutCommand',
188
- 'UpdateCommand',
189
- 'DeleteCommand',
190
- 'QueryCommand',
191
- 'ScanCommand',
192
- ];
193
-
194
- operations.forEach((op) => {
195
- logger.log(op, { TableName: 'Test' });
196
- });
197
-
198
- expect(mockLoggerFn).toHaveBeenCalledTimes(operations.length);
199
-
200
- operations.forEach((op, index) => {
201
- const loggedMessage = mockLoggerFn.mock.calls[index][0];
202
- expect(loggedMessage).toContain(`[DynamoDB] ${op}`);
203
- });
204
- });
205
-
206
- it('should handle complex nested objects in params and response', () => {
207
- const mockLoggerFn = jest.fn();
208
- const logger = createDynamoDBLogger({
209
- enabled: true,
210
- logParams: true,
211
- logResponse: true,
212
- loggerFn: mockLoggerFn,
213
- });
214
-
215
- const params = {
216
- TableName: 'Users',
217
- Item: {
218
- PK: 'USER#123',
219
- SK: 'PROFILE',
220
- nested: {
221
- level1: {
222
- level2: {
223
- value: 'deep',
224
- },
225
- },
226
- },
227
- },
228
- };
229
-
230
- const response = {
231
- Items: [
232
- { id: '1', data: { nested: 'value1' } },
233
- { id: '2', data: { nested: 'value2' } },
234
- ],
235
- };
236
-
237
- logger.log('QueryCommand', params, response);
238
-
239
- const loggedMessage = mockLoggerFn.mock.calls[0][0];
240
-
241
- expect(loggedMessage).toContain('"value": "deep"');
242
- expect(loggedMessage).toContain('"nested": "value1"');
243
- expect(loggedMessage).toContain('"nested": "value2"');
244
- });
245
- });
246
- });
@@ -1,175 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
-
3
- /**
4
- * Configuration for DynamoDB operation logging
5
- */
6
- export interface DynamoDBLoggerConfig {
7
- /**
8
- * Enable/disable logging. Default: false
9
- */
10
- enabled: boolean;
11
-
12
- /**
13
- * Log the request parameters. Default: true
14
- */
15
- logParams?: boolean;
16
-
17
- /**
18
- * Log the response data. Default: false
19
- */
20
- logResponse?: boolean;
21
-
22
- /**
23
- * Custom logger function. Default: console.log
24
- */
25
- loggerFn?: (message: string) => void;
26
- }
27
-
28
- /**
29
- * DynamoDB Logger interface
30
- */
31
- export type DynamoDBLogger = {
32
- /**
33
- * Log a DynamoDB operation with its parameters and optionally the response
34
- */
35
- log: <TParams, TResponse>(operationName: string, params: TParams, response?: TResponse) => void;
36
- };
37
-
38
- /**
39
- * Pure function to format log message
40
- * This function has no side effects and is easily testable
41
- */
42
- const formatLogMessage = <TParams, TResponse>(
43
- operationName: string,
44
- timestamp: string,
45
- params: TParams,
46
- response: TResponse | undefined,
47
- config: DynamoDBLoggerConfig
48
- ): string => {
49
- const parts: string[] = [
50
- '\n' + '='.repeat(80),
51
- `[DynamoDB] ${operationName} - ${timestamp}`,
52
- '='.repeat(80),
53
- ];
54
-
55
- if (config.logParams && params) {
56
- parts.push('\nšŸ“¤ Request Parameters:');
57
- parts.push(JSON.stringify(params, null, 2));
58
- }
59
-
60
- if (config.logResponse && response) {
61
- parts.push('\nšŸ“„ Response:');
62
- parts.push(JSON.stringify(response, null, 2));
63
- }
64
-
65
- parts.push('='.repeat(80) + '\n');
66
-
67
- return parts.join('\n');
68
- };
69
-
70
- /**
71
- * Creates a DynamoDB logger instance with the given configuration
72
- * This is the main factory function for creating loggers
73
- *
74
- * @example Basic logger with request parameters only
75
- * ```typescript
76
- * const logger = createDynamoDBLogger({
77
- * enabled: true,
78
- * logParams: true,
79
- * logResponse: false,
80
- * });
81
- * ```
82
- *
83
- * @example Full logging (params + response)
84
- * ```typescript
85
- * const logger = createDynamoDBLogger({
86
- * enabled: true,
87
- * logParams: true,
88
- * logResponse: true,
89
- * });
90
- * ```
91
- *
92
- * @example Using with a table
93
- * ```typescript
94
- * const logger = createDynamoDBLogger({
95
- * enabled: true,
96
- * logParams: true,
97
- * logResponse: true,
98
- * });
99
- *
100
- * const userTable = createTable({
101
- * tableName: 'Users',
102
- * client: dynamoDBClient,
103
- * schema: userSchema,
104
- * logger, // Pass logger to table
105
- * });
106
- *
107
- * // Operations will be logged automatically
108
- * const user = await userTable.get({ username: 'johndoe' }).execute();
109
- * ```
110
- *
111
- * @example Custom logger function (Winston, Pino, etc.)
112
- * ```typescript
113
- * import winston from 'winston';
114
- *
115
- * const winstonLogger = winston.createLogger({
116
- * level: 'info',
117
- * format: winston.format.json(),
118
- * transports: [new winston.transports.Console()],
119
- * });
120
- *
121
- * const logger = createDynamoDBLogger({
122
- * enabled: true,
123
- * logParams: true,
124
- * logResponse: true,
125
- * loggerFn: (message) => winstonLogger.info(message),
126
- * });
127
- * ```
128
- *
129
- * @example Output format
130
- * ```
131
- * ================================================================================
132
- * [DynamoDB] GetCommand - 2025-12-29T10:30:45.123Z
133
- * ================================================================================
134
- *
135
- * šŸ“¤ Request Parameters:
136
- * {
137
- * "TableName": "Users",
138
- * "Key": {
139
- * "PK": "USER#johndoe",
140
- * "SK": "PROFILE"
141
- * }
142
- * }
143
- *
144
- * šŸ“„ Response:
145
- * {
146
- * "Item": {
147
- * "PK": "USER#johndoe",
148
- * "SK": "PROFILE",
149
- * "username": "johndoe",
150
- * "email": "john@example.com"
151
- * }
152
- * }
153
- * ================================================================================
154
- * ```
155
- */
156
- export const createDynamoDBLogger = (config: DynamoDBLoggerConfig): DynamoDBLogger => {
157
- const logFn = config.loggerFn || console.log;
158
-
159
- return {
160
- log: <TParams, TResponse>(
161
- operationName: string,
162
- params: TParams,
163
- response?: TResponse
164
- ): void => {
165
- if (!config.enabled) {
166
- return;
167
- }
168
-
169
- const timestamp = new Date().toISOString();
170
- const message = formatLogMessage(operationName, timestamp, params, response, config);
171
-
172
- logFn(message);
173
- },
174
- };
175
- };