@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.
- package/CHANGELOG.md +14 -0
- package/dist/builders/query/create-query-builder.d.ts.map +1 -1
- package/dist/builders/query/create-query-builder.js +19 -2
- package/dist/builders/update/create-update-builder.d.ts.map +1 -1
- package/dist/builders/update/create-update-builder.js +25 -0
- package/dist/core/types.d.ts +5 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/utils/model-utils.d.ts +9 -3
- package/dist/utils/model-utils.d.ts.map +1 -1
- package/dist/utils/model-utils.js +15 -7
- package/package.json +19 -2
- package/eslint.config.mjs +0 -4
- package/jest.config.js +0 -11
- package/src/builders/README.md +0 -339
- package/src/builders/batch-get/README.md +0 -43
- package/src/builders/batch-get/create-batch-get-builder.test.ts +0 -193
- package/src/builders/batch-get/create-batch-get-builder.ts +0 -108
- package/src/builders/batch-get/index.ts +0 -2
- package/src/builders/batch-get/types.ts +0 -28
- package/src/builders/batch-write/README.md +0 -204
- package/src/builders/batch-write/create-batch-write-builder.test.ts +0 -173
- package/src/builders/batch-write/create-batch-write-builder.ts +0 -49
- package/src/builders/batch-write/index.ts +0 -2
- package/src/builders/batch-write/types.ts +0 -33
- package/src/builders/delete/create-delete-builder.test.ts +0 -294
- package/src/builders/delete/create-delete-builder.ts +0 -100
- package/src/builders/delete/index.ts +0 -2
- package/src/builders/delete/types.ts +0 -17
- package/src/builders/get/create-get-builder.test.ts +0 -272
- package/src/builders/get/create-get-builder.ts +0 -117
- package/src/builders/get/index.ts +0 -2
- package/src/builders/get/types.ts +0 -39
- package/src/builders/index.ts +0 -14
- package/src/builders/put/create-put-builder.test.ts +0 -213
- package/src/builders/put/create-put-builder.ts +0 -160
- package/src/builders/put/index.ts +0 -2
- package/src/builders/put/types.ts +0 -24
- package/src/builders/query/create-query-builder.test.ts +0 -770
- package/src/builders/query/create-query-builder.ts +0 -455
- package/src/builders/query/index.ts +0 -2
- package/src/builders/query/types.ts +0 -109
- package/src/builders/scan/create-scan-builder.test.ts +0 -416
- package/src/builders/scan/create-scan-builder.ts +0 -265
- package/src/builders/scan/index.ts +0 -2
- package/src/builders/scan/types.ts +0 -88
- package/src/builders/shared/conditions.ts +0 -58
- package/src/builders/shared/index.ts +0 -4
- package/src/builders/shared/operators.test.ts +0 -270
- package/src/builders/shared/operators.ts +0 -208
- package/src/builders/shared/projection.ts +0 -28
- package/src/builders/shared/types.ts +0 -101
- package/src/builders/transact-get/README.md +0 -167
- package/src/builders/transact-get/create-transact-get-builder.test.ts +0 -239
- package/src/builders/transact-get/create-transact-get-builder.ts +0 -67
- package/src/builders/transact-get/index.ts +0 -2
- package/src/builders/transact-get/types.ts +0 -31
- package/src/builders/transact-write/README.md +0 -202
- package/src/builders/transact-write/create-transact-write-builder.test.ts +0 -288
- package/src/builders/transact-write/create-transact-write-builder.ts +0 -124
- package/src/builders/transact-write/index.ts +0 -2
- package/src/builders/transact-write/types.ts +0 -71
- package/src/builders/update/create-update-builder.test.ts +0 -573
- package/src/builders/update/create-update-builder.ts +0 -375
- package/src/builders/update/index.ts +0 -2
- package/src/builders/update/types.ts +0 -63
- package/src/core/types.test.ts +0 -641
- package/src/core/types.ts +0 -366
- package/src/entity/create-entity-api.ts +0 -224
- package/src/entity/index.ts +0 -19
- package/src/entity/middleware/factories.ts +0 -15
- package/src/entity/middleware/types.ts +0 -7
- package/src/entity/middleware/with-middleware.ts +0 -37
- package/src/entity/types.ts +0 -79
- package/src/entity/validation/key-validation.ts +0 -34
- package/src/index.ts +0 -23
- package/src/table.ts +0 -116
- package/src/utils/dynamodb-logger.test.ts +0 -246
- package/src/utils/dynamodb-logger.ts +0 -175
- package/src/utils/model-utils.test.ts +0 -452
- package/src/utils/model-utils.ts +0 -188
- package/src/utils/zod-utils.test.ts +0 -363
- package/src/utils/zod-utils.ts +0 -56
- package/tests/integration/instagram-clone.integration.test.ts +0 -1019
- package/tests/integration/pagination-timestamps.integration.test.ts +0 -374
- package/tests/integration/transactions.integration.test.ts +0 -528
- package/tsconfig.json +0 -12
package/src/entity/types.ts
DELETED
|
@@ -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
|
-
};
|