@creator.co/wapi 1.7.9 → 1.7.10-alpha.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.
Files changed (44) hide show
  1. package/.eslintrc.cjs +19 -0
  2. package/README.md +1 -11
  3. package/dist/package-lock.json +830 -223
  4. package/dist/package.json +3 -1
  5. package/dist/src/BaseEvent/DynamoTransaction.d.ts +70 -0
  6. package/dist/src/BaseEvent/DynamoTransaction.js +104 -0
  7. package/dist/src/BaseEvent/DynamoTransaction.js.map +1 -0
  8. package/dist/src/BaseEvent/EventProcessor.js.map +1 -1
  9. package/dist/src/BaseEvent/Transaction.d.ts +3 -3
  10. package/dist/src/BaseEvent/Transaction.js +1 -1
  11. package/dist/src/BaseEvent/Transaction.js.map +1 -1
  12. package/dist/src/Cache/Redis.js.map +1 -1
  13. package/dist/src/Database/integrations/dynamo/DynamoDatabase.js.map +1 -1
  14. package/dist/src/Database/integrations/knex/KnexDatabase.js.map +1 -1
  15. package/dist/src/Database/integrations/kysely/KyselyDatabase.js.map +1 -1
  16. package/dist/src/Database/integrations/pgsql/PostgresDatabase.js.map +1 -1
  17. package/dist/src/Logger/Logger.js.map +1 -1
  18. package/dist/src/Publisher/Publisher.js.map +1 -1
  19. package/dist/src/Server/RouteResolver.d.ts +0 -1
  20. package/dist/src/Server/RouteResolver.js +0 -1
  21. package/dist/src/Server/RouteResolver.js.map +1 -1
  22. package/dist/src/Server/lib/container/Proxy.js.map +1 -1
  23. package/dist/src/Util/Utils.d.ts +15 -0
  24. package/dist/src/Util/Utils.js +41 -0
  25. package/dist/src/Util/Utils.js.map +1 -1
  26. package/package.json +3 -1
  27. package/src/BaseEvent/DynamoTransaction.ts +175 -0
  28. package/src/BaseEvent/EventProcessor.ts +1 -1
  29. package/src/BaseEvent/Transaction.ts +7 -3
  30. package/src/Cache/Redis.ts +1 -0
  31. package/src/Database/integrations/dynamo/DynamoDatabase.ts +1 -1
  32. package/src/Database/integrations/knex/KnexDatabase.ts +1 -1
  33. package/src/Database/integrations/kysely/KyselyDatabase.ts +3 -1
  34. package/src/Database/integrations/pgsql/PostgresDatabase.ts +2 -1
  35. package/src/Logger/Logger.ts +7 -7
  36. package/src/Publisher/Publisher.ts +5 -2
  37. package/src/Server/RouteResolver.ts +1 -1
  38. package/src/Server/lib/container/Proxy.ts +2 -2
  39. package/src/Util/AsyncSingleton.ts +1 -1
  40. package/src/Util/Utils.ts +38 -0
  41. package/tests/API/Utils.test.ts +100 -38
  42. package/tests/BaseEvent/DynamoTransaction.test.ts +272 -0
  43. package/tests/Logger/Logger.test.ts +1 -1
  44. package/tests/Test.utils.ts +5 -1
@@ -0,0 +1,175 @@
1
+ import type {
2
+ Context,
3
+ DynamoDBBatchResponse,
4
+ DynamoDBRecord,
5
+ DynamoDBStreamEvent,
6
+ } from 'aws-lambda'
7
+
8
+ import Transaction, { TransactionConfig } from './Transaction.js'
9
+ import Response, { ResponseErrorType } from '../API/Response.js'
10
+ import Globals from '../Globals.js'
11
+ import Utils from '../Util/Utils.js'
12
+
13
+ /**
14
+ * Interface representing a DynamoDB record with marshalled data.
15
+ * Extends the DynamoDBRecord interface.
16
+ * @property {object} Keys - The keys of the record.
17
+ * @property {object} OldImage - The old image of the record.
18
+ * @property {object} NewImage - The new image of the record.
19
+ */
20
+ export interface DynamoDBMarshalledRecord extends DynamoDBRecord {
21
+ marshalled: {
22
+ Keys?: object
23
+ OldImage?: object
24
+ NewImage?: object
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Defines a type for executing a transaction on DynamoDB.
30
+ * @param {Transaction<null, ResponseInnerType | ResponseErrorType, DynamoDBBatchResponse | null>} transaction - The transaction to execute.
31
+ * @param {DynamoDBMarshalledRecord} recordContent - The content of the DynamoDB record.
32
+ * @returns A promise that resolves to a response or a DynamoDB batch response.
33
+ */
34
+ export type DynamoTransactionExecution<ResponseInnerType> = (
35
+ transaction: Transaction<
36
+ null,
37
+ ResponseInnerType | ResponseErrorType,
38
+ DynamoDBBatchResponse | null
39
+ >,
40
+ recordContent: DynamoDBMarshalledRecord
41
+ ) => Promise<Response<ResponseInnerType | ResponseErrorType> | DynamoDBBatchResponse | null>
42
+
43
+ /**
44
+ * Represents a DynamoDB transaction handler that processes events from a DynamoDB stream.
45
+ * @template ResponseInnerType - The inner type of the response.
46
+ */
47
+ export default class DynamoTransaction<ResponseInnerType> {
48
+ /**
49
+ * A boolean flag indicating whether failures are allowed.
50
+ */
51
+ private readonly allowFailure: boolean
52
+
53
+ /**
54
+ * Readonly property that holds the transaction configuration.
55
+ */
56
+ private readonly config: TransactionConfig
57
+
58
+ /**
59
+ * The context object that provides information about the current execution context.
60
+ * @type {Context}
61
+ */
62
+ private readonly context: Context
63
+
64
+ /**
65
+ * Represents an event from a DynamoDB stream.
66
+ * @type {DynamoDBStreamEvent}
67
+ */
68
+ private readonly event: DynamoDBStreamEvent
69
+
70
+ /**
71
+ * Constructor for a TransactionHandler object.
72
+ * @param {DynamoDBStreamEvent} event - The DynamoDB stream event that triggered the transaction.
73
+ * @param {Context} context - The AWS Lambda context object.
74
+ * @param {TransactionConfig} [config] - Optional configuration for the transaction.
75
+ * @param {boolean} [allowFailure] - Flag to indicate whether to allow transaction failure.
76
+ * @returns None
77
+ */
78
+ constructor(
79
+ event: DynamoDBStreamEvent,
80
+ context: Context,
81
+ config?: TransactionConfig,
82
+ allowFailure?: boolean
83
+ ) {
84
+ this.event = event
85
+ this.context = context
86
+ this.config = config || {}
87
+ this.allowFailure = !!allowFailure
88
+ }
89
+
90
+ /**
91
+ * Processes the event execution and returns a response based on the outcome.
92
+ * @param {DynamoTransactionExecution<ResponseInnerType>} execution - The execution object to process.
93
+ * @returns {Promise<Response<ResponseErrorType | null> | null | DynamoDBBatchResponse>} A promise that resolves to a response object or null.
94
+ * @throws {Error} If the response code is not within the success range and failure is not allowed.
95
+ */
96
+ public async processEvent(
97
+ execution: DynamoTransactionExecution<ResponseInnerType>
98
+ ): Promise<Response<ResponseErrorType | null> | null | DynamoDBBatchResponse> {
99
+ const resp = await this.processRawEvent(execution)
100
+ if (
101
+ !this.allowFailure &&
102
+ resp &&
103
+ resp instanceof Response &&
104
+ !(resp.getCode() >= 200 && resp.getCode() < 300)
105
+ )
106
+ throw new Error(JSON.stringify(resp.getBody() || {}))
107
+ else if (resp) return resp
108
+ return null
109
+ }
110
+
111
+ /**
112
+ * Processes a raw event by executing a transaction on each record in the event.
113
+ * @param {DynamoTransactionExecution<ResponseInnerType>} execution - The transaction execution function.
114
+ * @returns {Promise<Response<ResponseErrorType | any> | null | DynamoDBBatchResponse>} A promise that resolves to a response object, null, or a DynamoDB batch response.
115
+ */
116
+ private async processRawEvent(
117
+ execution: DynamoTransactionExecution<ResponseInnerType>
118
+ ): Promise<Response<ResponseErrorType | any> | null | DynamoDBBatchResponse> {
119
+ // safe check for empty events?
120
+ if (this.event.Records && this.event.Records.length > 0) {
121
+ // Regular cleanup of tmp disk to avoid tmp overflow on reused lambdas
122
+ if (!this.config?.skipCleanTmp) await Utils.cleanTemporaryFolder()
123
+ // init transaction for all records
124
+ return await new Transaction<null, any, DynamoDBBatchResponse | null>(
125
+ this.event,
126
+ this.context,
127
+ {
128
+ ...this.config,
129
+ syncReturn: true,
130
+ }
131
+ ).execute(async transaction => {
132
+ // for each available event
133
+ const failureIDs: Array<string> = []
134
+ for (const eventRecordIdx in this.event.Records) {
135
+ const eventRecord = this.event.Records[eventRecordIdx]
136
+ const record = {
137
+ ...eventRecord,
138
+ marshalled: {
139
+ ...(eventRecord.dynamodb?.Keys
140
+ ? { Keys: Utils.ddbUnmarshall(eventRecord.dynamodb?.Keys) }
141
+ : {}),
142
+ ...(eventRecord.dynamodb?.OldImage
143
+ ? { OldImage: Utils.ddbUnmarshall(eventRecord.dynamodb?.OldImage) }
144
+ : {}),
145
+ ...(eventRecord.dynamodb?.NewImage
146
+ ? { NewImage: Utils.ddbUnmarshall(eventRecord.dynamodb?.NewImage) }
147
+ : {}),
148
+ },
149
+ }
150
+ // Call execution with marshalled item
151
+ const resp = await execution(transaction, record)
152
+ // check for failure
153
+ if (
154
+ !resp ||
155
+ (resp instanceof Response && !(resp?.getCode() >= 200 && resp?.getCode() < 300))
156
+ ) {
157
+ // response with failures or fail hard at first
158
+ if (this.allowFailure) failureIDs.push(eventRecord.eventID!)
159
+ else return resp
160
+ }
161
+ }
162
+ // not errored and loop ended - succeeded (might have failures)
163
+ if (this.allowFailure)
164
+ return {
165
+ batchItemFailures: failureIDs.map(id => ({ itemIdentifier: id })),
166
+ }
167
+ return Response.SuccessResponse(null)
168
+ })
169
+ } else
170
+ return Response.BadRequestResponse(
171
+ Globals.ErrorResponseNoRecords,
172
+ Globals.ErrorCode_NoRecords
173
+ )
174
+ }
175
+ }
@@ -67,7 +67,7 @@ export default class EventProcessor<ResponseInnerType> {
67
67
  * @returns {Promise<Response<ResponseErrorType> | null | SQSBatchResponse>} - A promise that resolves to the response object, or null if no response is available.
68
68
  * @throws {Error} - Throws an error if the response code is not within the range of 200 to 299 and failure is not allowed.
69
69
  */
70
- async processEvent(
70
+ public async processEvent(
71
71
  execution: EventProcessorExecution<ResponseInnerType>,
72
72
  doNotDecodeMessage?: boolean
73
73
  ): Promise<Response<ResponseErrorType | null> | null | SQSBatchResponse> {
@@ -1,4 +1,4 @@
1
- import type { APIGatewayEvent, Context, SQSEvent } from 'aws-lambda'
1
+ import type { APIGatewayEvent, Context, DynamoDBStreamEvent, SQSEvent } from 'aws-lambda'
2
2
 
3
3
  import Request from '../API/Request.js'
4
4
  import Response, { ResponseErrorType } from '../API/Response.js'
@@ -116,12 +116,16 @@ export default class Transaction<
116
116
 
117
117
  /**
118
118
  * Constructs a new instance of the Transaction class.
119
- * @param {APIGatewayEvent | SQSEvent} event - The event object passed to the Lambda function.
119
+ * @param {APIGatewayEvent | SQSEvent | DynamoDBStreamEvent} event - The event object passed to the Lambda function.
120
120
  * @param {Context} context - The context object passed to the Lambda function.
121
121
  * @param {TransactionConfig} [config] - Optional configuration object for the transaction.
122
122
  * @returns None
123
123
  */
124
- constructor(event: APIGatewayEvent | SQSEvent, context: Context, config?: TransactionConfig) {
124
+ constructor(
125
+ event: APIGatewayEvent | SQSEvent | DynamoDBStreamEvent,
126
+ context: Context,
127
+ config?: TransactionConfig
128
+ ) {
125
129
  const transactionId = context.awsRequestId
126
130
  ? context.awsRequestId
127
131
  : (<APIGatewayEvent>event).requestContext
@@ -35,6 +35,7 @@ export default class Redis {
35
35
  if (config.clusterMode) return Redis.redisClusterConnection(config)
36
36
  else return Redis.redisClientConnection(config)
37
37
  }
38
+
38
39
  /**
39
40
  * Establishes a connection to a Redis client based on the provided configuration
40
41
  * and holds it for reusability. If connection is detected closed, new connection is
@@ -10,7 +10,7 @@ export class DynamoDatabase extends Database<any> {
10
10
  private static dynamoProvider = DynamoDBClient
11
11
  public readonly client: DynamoDBClient
12
12
 
13
- public constructor(config: DbConfig<'dynamo'>) {
13
+ constructor(config: DbConfig<'dynamo'>) {
14
14
  super(config)
15
15
  this.client = this.providerFactory(config)
16
16
  }
@@ -26,7 +26,7 @@ export class KnexDatabase extends Database<KnexTransaction> {
26
26
  * @param {DbConfig<'knex'>} config - The configuration object for the Knex database.
27
27
  * @returns None
28
28
  */
29
- public constructor(config: DbConfig<'knex'>) {
29
+ constructor(config: DbConfig<'knex'>) {
30
30
  super(config)
31
31
 
32
32
  this.client = KnexDatabase.knexProvider(this.constructConfig(config))
@@ -49,7 +49,7 @@ export class KyselyDatabase<DBSchema = never> extends Database<KyselyTransaction
49
49
  * @param {DbConfig<'kysely'>} config - The configuration object for the database connection.
50
50
  * @returns None
51
51
  */
52
- public constructor(config: DbConfig<'kysely'>) {
52
+ constructor(config: DbConfig<'kysely'>) {
53
53
  super(config)
54
54
  this.pgClient = this.dialectFactory(config)
55
55
  this.client = this.providerFactory(this.pgClient)
@@ -66,6 +66,7 @@ export class KyselyDatabase<DBSchema = never> extends Database<KyselyTransaction
66
66
  public override async transaction(): Promise<KyselyTransaction<DBSchema>> {
67
67
  return KyselyTransactionImpl.newTransaction<DBSchema>(this.client, this, this.readClient)
68
68
  }
69
+
69
70
  /**
70
71
  * Creates a database dialect based on the provided configuration.
71
72
  * @param {DbBaseConfig} config - The configuration object for the database connection.
@@ -83,6 +84,7 @@ export class KyselyDatabase<DBSchema = never> extends Database<KyselyTransaction
83
84
  }),
84
85
  })
85
86
  }
87
+
86
88
  /**
87
89
  * Creates a provider for interacting with a database using the specified Postgres dialect.
88
90
  * @param {PostgresDialect} dialect - The Postgres dialect to use for the database connection.
@@ -29,7 +29,7 @@ export class PostgresDatabase extends Database<PostgresTransaction> {
29
29
  * @param {DbConfig<'pg'>} config - The configuration object for the Postgres database.
30
30
  * @returns None
31
31
  */
32
- public constructor(config: DbConfig<'pg'>) {
32
+ constructor(config: DbConfig<'pg'>) {
33
33
  super(config)
34
34
  this.client = this.providerFactory(config)
35
35
  if (config.readReplica) this.readClient = this.providerFactory(config.readReplica)
@@ -42,6 +42,7 @@ export class PostgresDatabase extends Database<PostgresTransaction> {
42
42
  public override async transaction(): Promise<PostgresTransaction> {
43
43
  return PostgresTransactionImpl.newTransaction(this.client, this, this.readClient)
44
44
  }
45
+
45
46
  private providerFactory(config: DbBaseConfig) {
46
47
  return new PostgresDatabase.pgProvider({
47
48
  host: config.host,
@@ -128,7 +128,7 @@ export default class Logger {
128
128
  * @param {...any} args - The arguments to be logged.
129
129
  * @returns None
130
130
  */
131
- debug(...args) {
131
+ public debug(...args) {
132
132
  this.processLog(LOG_LEVELS.DEBUG, args)
133
133
  }
134
134
 
@@ -137,7 +137,7 @@ export default class Logger {
137
137
  * @param {...any} args - The arguments to be logged.
138
138
  * @returns None
139
139
  */
140
- log(...args) {
140
+ public log(...args) {
141
141
  this.processLog(LOG_LEVELS.INFO, args)
142
142
  }
143
143
 
@@ -146,7 +146,7 @@ export default class Logger {
146
146
  * @param {...any} args - The message(s) to log.
147
147
  * @returns None
148
148
  */
149
- info(...args) {
149
+ public info(...args) {
150
150
  this.processLog(LOG_LEVELS.INFO, args)
151
151
  }
152
152
 
@@ -155,7 +155,7 @@ export default class Logger {
155
155
  * @param {...any} args - The arguments to be logged as a warning message.
156
156
  * @returns None
157
157
  */
158
- warning(...args) {
158
+ public warning(...args) {
159
159
  this.processLog(LOG_LEVELS.WARN, args)
160
160
  }
161
161
 
@@ -164,7 +164,7 @@ export default class Logger {
164
164
  * @param {...any} args - The arguments to be logged.
165
165
  * @returns None
166
166
  */
167
- warn(...args) {
167
+ public warn(...args) {
168
168
  this.processLog(LOG_LEVELS.WARN, args)
169
169
  }
170
170
 
@@ -173,7 +173,7 @@ export default class Logger {
173
173
  * @param {...any} args - The arguments to log as an error message.
174
174
  * @returns None
175
175
  */
176
- error(...args) {
176
+ public error(...args) {
177
177
  this.processLog(LOG_LEVELS.ERROR, args)
178
178
  }
179
179
 
@@ -183,7 +183,7 @@ export default class Logger {
183
183
  * @param {...any} args - Additional arguments to include in the log.
184
184
  * @returns None
185
185
  */
186
- exception(exception, ...args) {
186
+ public exception(exception, ...args) {
187
187
  this.iexception(exception, args)
188
188
  }
189
189
 
@@ -58,7 +58,7 @@ export default class Publisher {
58
58
  * @param {object} [additionalProps] - Additional properties to include in the publish command.
59
59
  * @returns {Promise<PublishCommandOutput>} - A promise that resolves to the response from the publish command.
60
60
  */
61
- async publishOnTopic(
61
+ public async publishOnTopic(
62
62
  messageObject: any,
63
63
  topic: string,
64
64
  additionalProps?: object
@@ -86,7 +86,10 @@ export default class Publisher {
86
86
  * @param {string} phone - The phone number to publish the message to.
87
87
  * @returns {Promise<PublishCommandOutput>} - A promise that resolves to the response from the publish command.
88
88
  */
89
- async publishSMS(message: string, phone: string): Promise<PublishCommandOutput | undefined> {
89
+ public async publishSMS(
90
+ message: string,
91
+ phone: string
92
+ ): Promise<PublishCommandOutput | undefined> {
90
93
  let resp: undefined | PublishCommandOutput = undefined
91
94
  try {
92
95
  this.connect()
@@ -88,7 +88,7 @@ export default class RouteResolver {
88
88
  * @param {RouterConfig} config - The configuration object for the router.
89
89
  * @returns None
90
90
  */
91
- constructor(readonly config: RouterConfig) {
91
+ constructor(config: RouterConfig) {
92
92
  this.routes = {}
93
93
  this.buildRoutes(config)
94
94
  }
@@ -91,7 +91,7 @@ export default class Proxy {
91
91
  * Loads the necessary components and initializes the application.
92
92
  * @returns None
93
93
  */
94
- async load() {
94
+ public async load() {
95
95
  await this.startListeners()
96
96
  this.installRoutes()
97
97
  }
@@ -101,7 +101,7 @@ export default class Proxy {
101
101
  * @param {any} [err] - Optional error object to pass to the stopListeners method.
102
102
  * @returns {Promise<void>} - A promise that resolves once the listeners have been stopped.
103
103
  */
104
- async unload(err?: any) {
104
+ public async unload(err?: any) {
105
105
  await this.stopListeners(err)
106
106
  }
107
107
 
@@ -17,7 +17,7 @@ export default class AsyncSingleton<T, R> {
17
17
  * @param factory The factory function that creates the instance.
18
18
  * @param checker The function that checks if the instance is valid.
19
19
  */
20
- public constructor(
20
+ constructor(
21
21
  factory: (r: R) => Promise<T>,
22
22
  checker: (value: T) => Promise<boolean> = async () => true
23
23
  ) {
package/src/Util/Utils.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import child_process from 'child_process'
2
2
 
3
+ import { convertToAttr, marshall, unmarshall } from '@aws-sdk/util-dynamodb'
4
+
3
5
  /**
4
6
  * Utility class containing various static methods for common operations.
5
7
  */
@@ -91,4 +93,40 @@ export default class Utils {
91
93
  }
92
94
  })
93
95
  }
96
+
97
+ /**
98
+ * Marshalls the given item into a DynamoDB format.
99
+ * If the item is an array, it maps over each element and marshalls it recursively.
100
+ * If the item is an object, it marshalls the object using the marshall function with options to remove undefined values and convert class instances to maps.
101
+ * If the item is neither an array nor an object, it converts the item to an attribute.
102
+ * @param {any} item - The item to be marshalled.
103
+ * @returns The marshalled item in DynamoDB format.
104
+ */
105
+ public static ddbMarshall<T>(item: T, rec?: boolean) {
106
+ if (Array.isArray(item)) return { L: item.map(_i => this.ddbMarshall(_i, true)) }
107
+ else if (typeof item === 'object' && isNaN(parseInt(item as any))) {
108
+ const marshalled = marshall(item, {
109
+ removeUndefinedValues: true,
110
+ convertClassInstanceToMap: true,
111
+ })
112
+ if (rec) return { M: marshalled }
113
+ else return marshalled
114
+ } else
115
+ return convertToAttr(item, { removeUndefinedValues: true, convertClassInstanceToMap: true })
116
+ }
117
+
118
+ /**
119
+ * Recursively unmarshalls a DynamoDB item by converting it into a plain JavaScript object.
120
+ * @param {any} item - The DynamoDB item to unmarshall.
121
+ * @returns {any} The unmarshalled JavaScript object.
122
+ */
123
+ public static ddbUnmarshall(item) {
124
+ if (!item && item !== false) return null
125
+ if (Array.isArray(item)) {
126
+ return item.map(_item => this.ddbUnmarshall(_item))
127
+ } else if (typeof item === 'object') {
128
+ return unmarshall(item)
129
+ }
130
+ return item
131
+ }
94
132
  }