@multiplayer-app/ai-agent-node 0.0.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.
- package/.env.example +45 -0
- package/README.md +611 -0
- package/config.example.json +73 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +44 -0
- package/dist/config.js.map +1 -0
- package/dist/helpers/AIHelper.d.ts +23 -0
- package/dist/helpers/AIHelper.d.ts.map +1 -0
- package/dist/helpers/AIHelper.js +326 -0
- package/dist/helpers/AIHelper.js.map +1 -0
- package/dist/helpers/AIHelper.test.d.ts +2 -0
- package/dist/helpers/AIHelper.test.d.ts.map +1 -0
- package/dist/helpers/AIHelper.test.js +332 -0
- package/dist/helpers/AIHelper.test.js.map +1 -0
- package/dist/helpers/ConfigHelper.d.ts +20 -0
- package/dist/helpers/ConfigHelper.d.ts.map +1 -0
- package/dist/helpers/ConfigHelper.js +118 -0
- package/dist/helpers/ConfigHelper.js.map +1 -0
- package/dist/helpers/ContextLimiter.d.ts +82 -0
- package/dist/helpers/ContextLimiter.d.ts.map +1 -0
- package/dist/helpers/ContextLimiter.js +165 -0
- package/dist/helpers/ContextLimiter.js.map +1 -0
- package/dist/helpers/FileHelper.d.ts +31 -0
- package/dist/helpers/FileHelper.d.ts.map +1 -0
- package/dist/helpers/FileHelper.js +175 -0
- package/dist/helpers/FileHelper.js.map +1 -0
- package/dist/helpers/SetupHelper.d.ts +5 -0
- package/dist/helpers/SetupHelper.d.ts.map +1 -0
- package/dist/helpers/SetupHelper.js +32 -0
- package/dist/helpers/SetupHelper.js.map +1 -0
- package/dist/helpers/index.d.ts +6 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +6 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/libs/index.d.ts +4 -0
- package/dist/libs/index.d.ts.map +1 -0
- package/dist/libs/index.js +4 -0
- package/dist/libs/index.js.map +1 -0
- package/dist/libs/kafka/config.d.ts +5 -0
- package/dist/libs/kafka/config.d.ts.map +1 -0
- package/dist/libs/kafka/config.js +5 -0
- package/dist/libs/kafka/config.js.map +1 -0
- package/dist/libs/kafka/consumer.d.ts +16 -0
- package/dist/libs/kafka/consumer.d.ts.map +1 -0
- package/dist/libs/kafka/consumer.js +126 -0
- package/dist/libs/kafka/consumer.js.map +1 -0
- package/dist/libs/kafka/index.d.ts +3 -0
- package/dist/libs/kafka/index.d.ts.map +1 -0
- package/dist/libs/kafka/index.js +3 -0
- package/dist/libs/kafka/index.js.map +1 -0
- package/dist/libs/kafka/kafka.d.ts +3 -0
- package/dist/libs/kafka/kafka.d.ts.map +1 -0
- package/dist/libs/kafka/kafka.js +24 -0
- package/dist/libs/kafka/kafka.js.map +1 -0
- package/dist/libs/kafka/producer.d.ts +11 -0
- package/dist/libs/kafka/producer.d.ts.map +1 -0
- package/dist/libs/kafka/producer.js +44 -0
- package/dist/libs/kafka/producer.js.map +1 -0
- package/dist/libs/logger/config.d.ts +5 -0
- package/dist/libs/logger/config.d.ts.map +1 -0
- package/dist/libs/logger/config.js +6 -0
- package/dist/libs/logger/config.js.map +1 -0
- package/dist/libs/logger/index.d.ts +10 -0
- package/dist/libs/logger/index.d.ts.map +1 -0
- package/dist/libs/logger/index.js +20 -0
- package/dist/libs/logger/index.js.map +1 -0
- package/dist/libs/logger/kafkajs-logger-creator.d.ts +12 -0
- package/dist/libs/logger/kafkajs-logger-creator.d.ts.map +1 -0
- package/dist/libs/logger/kafkajs-logger-creator.js +29 -0
- package/dist/libs/logger/kafkajs-logger-creator.js.map +1 -0
- package/dist/libs/logger/logger.d.ts +42 -0
- package/dist/libs/logger/logger.d.ts.map +1 -0
- package/dist/libs/logger/logger.js +44 -0
- package/dist/libs/logger/logger.js.map +1 -0
- package/dist/libs/s3/config.d.ts +7 -0
- package/dist/libs/s3/config.d.ts.map +1 -0
- package/dist/libs/s3/config.js +7 -0
- package/dist/libs/s3/config.js.map +1 -0
- package/dist/libs/s3/index.d.ts +4 -0
- package/dist/libs/s3/index.d.ts.map +1 -0
- package/dist/libs/s3/index.js +4 -0
- package/dist/libs/s3/index.js.map +1 -0
- package/dist/libs/s3/s3.lib.d.ts +25 -0
- package/dist/libs/s3/s3.lib.d.ts.map +1 -0
- package/dist/libs/s3/s3.lib.js +202 -0
- package/dist/libs/s3/s3.lib.js.map +1 -0
- package/dist/processors/ChatProcessor.d.ts +66 -0
- package/dist/processors/ChatProcessor.d.ts.map +1 -0
- package/dist/processors/ChatProcessor.js +610 -0
- package/dist/processors/ChatProcessor.js.map +1 -0
- package/dist/processors/ModelsProcessor.d.ts +11 -0
- package/dist/processors/ModelsProcessor.d.ts.map +1 -0
- package/dist/processors/ModelsProcessor.js +30 -0
- package/dist/processors/ModelsProcessor.js.map +1 -0
- package/dist/processors/index.d.ts +3 -0
- package/dist/processors/index.d.ts.map +1 -0
- package/dist/processors/index.js +3 -0
- package/dist/processors/index.js.map +1 -0
- package/dist/services/AIService.d.ts +48 -0
- package/dist/services/AIService.d.ts.map +1 -0
- package/dist/services/AIService.js +196 -0
- package/dist/services/AIService.js.map +1 -0
- package/dist/services/InternalEventsHandler.d.ts +21 -0
- package/dist/services/InternalEventsHandler.d.ts.map +1 -0
- package/dist/services/InternalEventsHandler.js +56 -0
- package/dist/services/InternalEventsHandler.js.map +1 -0
- package/dist/services/KafkaService.d.ts +35 -0
- package/dist/services/KafkaService.d.ts.map +1 -0
- package/dist/services/KafkaService.js +120 -0
- package/dist/services/KafkaService.js.map +1 -0
- package/dist/services/ModelFetcher.d.ts +54 -0
- package/dist/services/ModelFetcher.d.ts.map +1 -0
- package/dist/services/ModelFetcher.js +247 -0
- package/dist/services/ModelFetcher.js.map +1 -0
- package/dist/services/RedisService.d.ts +90 -0
- package/dist/services/RedisService.d.ts.map +1 -0
- package/dist/services/RedisService.js +236 -0
- package/dist/services/RedisService.js.map +1 -0
- package/dist/services/SocketService.d.ts +39 -0
- package/dist/services/SocketService.d.ts.map +1 -0
- package/dist/services/SocketService.js +128 -0
- package/dist/services/SocketService.js.map +1 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +7 -0
- package/dist/services/index.js.map +1 -0
- package/dist/store/AgentStore.d.ts +48 -0
- package/dist/store/AgentStore.d.ts.map +1 -0
- package/dist/store/AgentStore.js +98 -0
- package/dist/store/AgentStore.js.map +1 -0
- package/dist/store/ArtifactStore.d.ts +13 -0
- package/dist/store/ArtifactStore.d.ts.map +1 -0
- package/dist/store/ArtifactStore.js +27 -0
- package/dist/store/ArtifactStore.js.map +1 -0
- package/dist/store/ConfigStore.d.ts +89 -0
- package/dist/store/ConfigStore.d.ts.map +1 -0
- package/dist/store/ConfigStore.js +214 -0
- package/dist/store/ConfigStore.js.map +1 -0
- package/dist/store/ConfigStore.test.d.ts +2 -0
- package/dist/store/ConfigStore.test.d.ts.map +1 -0
- package/dist/store/ConfigStore.test.js +259 -0
- package/dist/store/ConfigStore.test.js.map +1 -0
- package/dist/store/ModelStore.d.ts +44 -0
- package/dist/store/ModelStore.d.ts.map +1 -0
- package/dist/store/ModelStore.js +81 -0
- package/dist/store/ModelStore.js.map +1 -0
- package/dist/store/ModelStore.test.d.ts +2 -0
- package/dist/store/ModelStore.test.d.ts.map +1 -0
- package/dist/store/ModelStore.test.js +390 -0
- package/dist/store/ModelStore.test.js.map +1 -0
- package/dist/store/index.d.ts +5 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +5 -0
- package/dist/store/index.js.map +1 -0
- package/dist/tools/generateChartTool.d.ts +24 -0
- package/dist/tools/generateChartTool.d.ts.map +1 -0
- package/dist/tools/generateChartTool.js +124 -0
- package/dist/tools/generateChartTool.js.map +1 -0
- package/dist/tools/proposeFormValuesTool.d.ts +35 -0
- package/dist/tools/proposeFormValuesTool.d.ts.map +1 -0
- package/dist/tools/proposeFormValuesTool.js +56 -0
- package/dist/tools/proposeFormValuesTool.js.map +1 -0
- package/package.json +71 -0
- package/src/config.ts +46 -0
- package/src/helpers/AIHelper.test.ts +375 -0
- package/src/helpers/AIHelper.ts +353 -0
- package/src/helpers/ConfigHelper.ts +130 -0
- package/src/helpers/ContextLimiter.ts +228 -0
- package/src/helpers/FileHelper.ts +197 -0
- package/src/helpers/SetupHelper.ts +35 -0
- package/src/helpers/index.ts +5 -0
- package/src/index.ts +18 -0
- package/src/libs/index.ts +3 -0
- package/src/libs/kafka/config.ts +4 -0
- package/src/libs/kafka/consumer.ts +161 -0
- package/src/libs/kafka/index.ts +2 -0
- package/src/libs/kafka/kafka.ts +27 -0
- package/src/libs/kafka/producer.ts +48 -0
- package/src/libs/logger/config.ts +4 -0
- package/src/libs/logger/index.ts +21 -0
- package/src/libs/logger/kafkajs-logger-creator.ts +28 -0
- package/src/libs/logger/logger.ts +60 -0
- package/src/libs/s3/config.ts +7 -0
- package/src/libs/s3/index.ts +3 -0
- package/src/libs/s3/s3.lib.ts +284 -0
- package/src/processors/ChatProcessor.ts +713 -0
- package/src/processors/ModelsProcessor.ts +34 -0
- package/src/processors/index.ts +2 -0
- package/src/services/AIService.ts +241 -0
- package/src/services/InternalEventsHandler.ts +61 -0
- package/src/services/KafkaService.ts +142 -0
- package/src/services/ModelFetcher.ts +286 -0
- package/src/services/RedisService.ts +285 -0
- package/src/services/SocketService.ts +153 -0
- package/src/services/index.ts +6 -0
- package/src/store/AgentStore.ts +138 -0
- package/src/store/ArtifactStore.ts +29 -0
- package/src/store/ConfigStore.test.ts +314 -0
- package/src/store/ConfigStore.ts +239 -0
- package/src/store/ModelStore.test.ts +473 -0
- package/src/store/ModelStore.ts +93 -0
- package/src/store/index.ts +4 -0
- package/src/tools/generateChartTool.ts +131 -0
- package/src/tools/proposeFormValuesTool.ts +67 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { IHeaders, Producer, ProducerConfig } from 'kafkajs'
|
|
2
|
+
import { kafka } from './kafka'
|
|
3
|
+
|
|
4
|
+
export class KafkaProducer {
|
|
5
|
+
private producer: Producer
|
|
6
|
+
private _isConnected = false
|
|
7
|
+
constructor(config: ProducerConfig = {}) {
|
|
8
|
+
this.producer = kafka.producer(config)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public async connect() {
|
|
12
|
+
try {
|
|
13
|
+
await this.producer.connect()
|
|
14
|
+
this._isConnected = true
|
|
15
|
+
} catch (err) {
|
|
16
|
+
this._isConnected = false
|
|
17
|
+
throw err
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public isConnected() {
|
|
22
|
+
return this._isConnected
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public async disconnect() {
|
|
26
|
+
await this.producer.disconnect()
|
|
27
|
+
this._isConnected = false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async send(topic: string, value: Record<string, any>, key?: string, headers?: IHeaders) {
|
|
31
|
+
try {
|
|
32
|
+
await this.producer.send({
|
|
33
|
+
topic,
|
|
34
|
+
messages: [
|
|
35
|
+
{
|
|
36
|
+
key,
|
|
37
|
+
value: JSON.stringify(value),
|
|
38
|
+
headers: headers
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
})
|
|
42
|
+
this._isConnected = true
|
|
43
|
+
} catch (err) {
|
|
44
|
+
this._isConnected = false
|
|
45
|
+
throw err
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const NODE_ENV = process.env.NODE_ENV || 'development'
|
|
2
|
+
export const isProduction = NODE_ENV === 'production'
|
|
3
|
+
export const APP_NAME = process.env.npm_package_name?.split('/').pop() as string || 'tests'
|
|
4
|
+
export const LOG_LEVEL = process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug')
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getLogger, setLogger, type AgentLogger } from './logger'
|
|
2
|
+
export { getLogger, setLogger, type AgentLogger }
|
|
3
|
+
export * from './kafkajs-logger-creator'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Logger object for backward compatibility.
|
|
7
|
+
* All method calls proxy to the current logger instance.
|
|
8
|
+
*/
|
|
9
|
+
export const logger = new Proxy({} as AgentLogger, {
|
|
10
|
+
get(_target, prop) {
|
|
11
|
+
const currentLogger = getLogger()
|
|
12
|
+
const method = (currentLogger as any)[prop]
|
|
13
|
+
if (typeof method === 'function') {
|
|
14
|
+
return method.bind(currentLogger)
|
|
15
|
+
}
|
|
16
|
+
// Return no-op for missing methods
|
|
17
|
+
return () => {}
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export default logger
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getLogger } from './logger'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a KafkaJS log creator that uses the injected logger.
|
|
5
|
+
* If no logger is set, logs are silently discarded.
|
|
6
|
+
*
|
|
7
|
+
* @param logLevelConverter - Function to convert KafkaJS log levels to logger levels
|
|
8
|
+
* @returns KafkaJS log creator function
|
|
9
|
+
*/
|
|
10
|
+
export const KafkaJsLogCreator = (logLevelConverter: any) => (level: any) => {
|
|
11
|
+
const logger = getLogger()
|
|
12
|
+
|
|
13
|
+
return ({ level, log }) => {
|
|
14
|
+
const { message, ...extra } = log
|
|
15
|
+
const logLevel = logLevelConverter(level)
|
|
16
|
+
|
|
17
|
+
// Use the logger's error, warn, info, or debug method based on level
|
|
18
|
+
if (logLevel === 'error' && logger.error) {
|
|
19
|
+
logger.error({ extra }, message)
|
|
20
|
+
} else if (logLevel === 'warn' && logger.warn) {
|
|
21
|
+
logger.warn({ extra }, message)
|
|
22
|
+
} else if (logLevel === 'info' && logger.info) {
|
|
23
|
+
logger.info({ extra }, message)
|
|
24
|
+
} else if (logLevel === 'debug' && logger.debug) {
|
|
25
|
+
logger.debug({ extra }, message)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger interface for AI Agent Node operations.
|
|
3
|
+
* Compatible with bunyan-style loggers and most common Node.js loggers (pino, winston, bunyan, etc.)
|
|
4
|
+
*/
|
|
5
|
+
export interface AgentLogger {
|
|
6
|
+
info(messageOrFields: string | object, message?: string): void;
|
|
7
|
+
error(messageOrFields: string | object | Error | unknown, message?: string | unknown): void;
|
|
8
|
+
error(message: string, error: unknown): void;
|
|
9
|
+
warn(messageOrFields: string | object, message?: string): void;
|
|
10
|
+
warn(message: string, fields: object): void;
|
|
11
|
+
debug(messageOrFields: string | object, message?: string): void;
|
|
12
|
+
debug(message: string, fields: object): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* No-op logger that discards all log messages.
|
|
17
|
+
* Used as default when no logger is provided.
|
|
18
|
+
*/
|
|
19
|
+
const noOpLogger: AgentLogger = {
|
|
20
|
+
info: () => {},
|
|
21
|
+
error: () => {},
|
|
22
|
+
warn: () => {},
|
|
23
|
+
debug: () => {},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let logger: AgentLogger = noOpLogger;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Sets a custom logger for AI Agent Node operations.
|
|
30
|
+
* If not called, all logging is silently discarded.
|
|
31
|
+
*
|
|
32
|
+
* @param customLogger - Logger instance implementing AgentLogger interface
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { setLogger } from '@multiplayer-app/ai-agent-node';
|
|
37
|
+
* import bunyan from 'bunyan';
|
|
38
|
+
*
|
|
39
|
+
* setLogger(bunyan.createLogger({ name: 'my-app' }));
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import { setLogger } from '@multiplayer-app/ai-agent-node';
|
|
45
|
+
* import pino from 'pino';
|
|
46
|
+
*
|
|
47
|
+
* setLogger(pino());
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function setLogger(customLogger: AgentLogger): void {
|
|
51
|
+
logger = customLogger;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Gets the current logger instance.
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
export function getLogger(): AgentLogger {
|
|
59
|
+
return logger;
|
|
60
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const S3_HOST = process.env.S3_HOST || 'https://s3.amazonaws.com'
|
|
2
|
+
export const S3_EXPORT_HOST = process.env.S3_EXPORT_HOST || S3_HOST
|
|
3
|
+
export const S3_PRESIGNED_URL_EXPIRES = Number(process.env.S3_PRESIGNED_URL_EXPIRES) || 120
|
|
4
|
+
|
|
5
|
+
export const AWS_REGION = process.env.AWS_REGION as string
|
|
6
|
+
export const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID as string
|
|
7
|
+
export const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY as string
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
S3,
|
|
3
|
+
GetObjectCommand,
|
|
4
|
+
PutObjectCommand,
|
|
5
|
+
CompleteMultipartUploadOutput,
|
|
6
|
+
ObjectCannedACL, GetObjectOutput,
|
|
7
|
+
GetObjectCommandOutput, ListObjectsV2CommandOutput,
|
|
8
|
+
ListObjectsV2Command,
|
|
9
|
+
HeadBucketCommand,
|
|
10
|
+
CreateBucketCommand,
|
|
11
|
+
} from '@aws-sdk/client-s3'
|
|
12
|
+
import { S3RequestPresigner } from '@aws-sdk/s3-request-presigner'
|
|
13
|
+
import { createRequest } from '@aws-sdk/util-create-request'
|
|
14
|
+
import { formatUrl } from '@aws-sdk/util-format-url'
|
|
15
|
+
import { Upload } from '@aws-sdk/lib-storage'
|
|
16
|
+
import stream from 'stream'
|
|
17
|
+
import {
|
|
18
|
+
S3_HOST,
|
|
19
|
+
S3_PRESIGNED_URL_EXPIRES,
|
|
20
|
+
AWS_REGION,
|
|
21
|
+
} from './config'
|
|
22
|
+
import logger from '../logger'
|
|
23
|
+
|
|
24
|
+
const S3Client = new S3({
|
|
25
|
+
...S3_HOST ? {
|
|
26
|
+
endpoint: S3_HOST,
|
|
27
|
+
forcePathStyle: true,
|
|
28
|
+
}
|
|
29
|
+
: {},
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const signer = new S3RequestPresigner({
|
|
33
|
+
...S3Client.config,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export const uploadFile = (Key: string, Bucket: string, Body: any, Expires?: Date) => {
|
|
37
|
+
return S3Client.putObject({
|
|
38
|
+
Key,
|
|
39
|
+
Bucket,
|
|
40
|
+
Body,
|
|
41
|
+
Expires,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const downloadFile = (Key: string, Bucket: string): Promise<GetObjectOutput> => {
|
|
46
|
+
const command = new GetObjectCommand({
|
|
47
|
+
Bucket,
|
|
48
|
+
Key,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return S3Client.send(command)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const downloadFileAsString = async (
|
|
55
|
+
Key: string,
|
|
56
|
+
Bucket: string,
|
|
57
|
+
): Promise<string | undefined> => {
|
|
58
|
+
try {
|
|
59
|
+
const command = new GetObjectCommand({
|
|
60
|
+
Bucket,
|
|
61
|
+
Key,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const response = await S3Client.send(command)
|
|
65
|
+
return response.Body?.transformToString()
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if ((err as any)?.name === 'NoSuchKey') {
|
|
68
|
+
return undefined
|
|
69
|
+
}
|
|
70
|
+
throw err
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const downloadFileAsByteArray = async (Key: string, Bucket: string): Promise<Uint8Array | undefined> => {
|
|
75
|
+
try {
|
|
76
|
+
const command = new GetObjectCommand({
|
|
77
|
+
Bucket,
|
|
78
|
+
Key,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const response = await S3Client.send(command)
|
|
82
|
+
return response.Body?.transformToByteArray()
|
|
83
|
+
} catch (err) {
|
|
84
|
+
if ((err as any)?.name === 'NoSuchKey') {
|
|
85
|
+
return undefined
|
|
86
|
+
}
|
|
87
|
+
throw err
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const streamUpload = (Key: string, Bucket: string, ACL?: ObjectCannedACL) => {
|
|
92
|
+
const passThroughStream = new stream.PassThrough()
|
|
93
|
+
|
|
94
|
+
const options: any = {
|
|
95
|
+
client: S3Client,
|
|
96
|
+
params: {
|
|
97
|
+
Bucket,
|
|
98
|
+
Key,
|
|
99
|
+
Body: passThroughStream,
|
|
100
|
+
},
|
|
101
|
+
leavePartsOnError: false,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (ACL) {
|
|
105
|
+
options.params.ACL = ACL
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const upload = new Upload(options)
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
writeStream: passThroughStream,
|
|
112
|
+
promise: upload.done() as Promise<CompleteMultipartUploadOutput>,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const getPresignedUploadUrl = async (
|
|
117
|
+
Key: string,
|
|
118
|
+
Bucket: string,
|
|
119
|
+
expiresIn?: number,
|
|
120
|
+
) => {
|
|
121
|
+
const request = await createRequest(
|
|
122
|
+
S3Client,
|
|
123
|
+
new PutObjectCommand({ Key, Bucket }),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
const signedUrl = formatUrl(await signer.presign(
|
|
127
|
+
request,
|
|
128
|
+
{
|
|
129
|
+
expiresIn: expiresIn || S3_PRESIGNED_URL_EXPIRES,
|
|
130
|
+
},
|
|
131
|
+
))
|
|
132
|
+
|
|
133
|
+
return signedUrl
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const getPresignedDownloadUrl = async (Key: string, Bucket: string) => {
|
|
137
|
+
const request = await createRequest(
|
|
138
|
+
S3Client,
|
|
139
|
+
new GetObjectCommand({ Key, Bucket }),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
const signedUrl = formatUrl(
|
|
143
|
+
await signer.presign(
|
|
144
|
+
request,
|
|
145
|
+
{
|
|
146
|
+
expiresIn: S3_PRESIGNED_URL_EXPIRES,
|
|
147
|
+
},
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return signedUrl
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const getDownloadUrl = (Key: string, Bucket: string) => {
|
|
155
|
+
if (S3_HOST) {
|
|
156
|
+
return `${S3_HOST}/${Bucket}/${Key}`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const regionString = AWS_REGION.includes('us-east-1') ? '' : '-' + AWS_REGION
|
|
160
|
+
return `https://${Bucket}.s3${regionString}.amazonaws.com/${Key}`
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const copy = (
|
|
164
|
+
Bucket: string,
|
|
165
|
+
KeyFrom: string,
|
|
166
|
+
KeyTo: string,
|
|
167
|
+
) => {
|
|
168
|
+
return S3Client.copyObject({
|
|
169
|
+
CopySource: `/${Bucket}/${KeyFrom}`,
|
|
170
|
+
Bucket,
|
|
171
|
+
Key: KeyTo,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export const copyBetweenBuckets = (
|
|
176
|
+
BucketFrom: string,
|
|
177
|
+
KeyFrom: string,
|
|
178
|
+
BucketTo: string,
|
|
179
|
+
KeyTo: string,
|
|
180
|
+
) => {
|
|
181
|
+
return S3Client.copyObject({
|
|
182
|
+
CopySource: `/${BucketFrom}/${KeyFrom}`,
|
|
183
|
+
Bucket: BucketTo,
|
|
184
|
+
Key: KeyTo,
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export const getObject = async (
|
|
189
|
+
Bucket: string,
|
|
190
|
+
Key: string,
|
|
191
|
+
): Promise<GetObjectCommandOutput> => {
|
|
192
|
+
return S3Client.getObject({
|
|
193
|
+
Bucket,
|
|
194
|
+
Key,
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const listObjectsByPrefix = async (
|
|
199
|
+
Bucket: string,
|
|
200
|
+
Prefix: string,
|
|
201
|
+
): Promise<ListObjectsV2CommandOutput> => {
|
|
202
|
+
return S3Client.send(
|
|
203
|
+
new ListObjectsV2Command({
|
|
204
|
+
Bucket,
|
|
205
|
+
Prefix,
|
|
206
|
+
}),
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const headObject = async (
|
|
211
|
+
Bucket: string,
|
|
212
|
+
Key: string,
|
|
213
|
+
): Promise<GetObjectCommandOutput> => {
|
|
214
|
+
return S3Client.headObject({
|
|
215
|
+
Bucket,
|
|
216
|
+
Key,
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export const deleteObject = async (
|
|
221
|
+
Bucket: string,
|
|
222
|
+
Key: string,
|
|
223
|
+
): Promise<GetObjectCommandOutput> => {
|
|
224
|
+
return S3Client.deleteObject({
|
|
225
|
+
Bucket,
|
|
226
|
+
Key,
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const deleteObjectsByPrefix = async (
|
|
231
|
+
Bucket: string,
|
|
232
|
+
Prefix: string,
|
|
233
|
+
ContinuationToken?: string,
|
|
234
|
+
): Promise<void> => {
|
|
235
|
+
const objects = await S3Client.listObjectsV2({ Bucket, Prefix, ContinuationToken })
|
|
236
|
+
if (!objects.Contents?.length) return
|
|
237
|
+
|
|
238
|
+
const deleteParams = {
|
|
239
|
+
Bucket,
|
|
240
|
+
Delete: { Objects: [] as { Key: string }[] },
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
objects.Contents.forEach(({ Key }) => {
|
|
244
|
+
if (!Key) return
|
|
245
|
+
deleteParams.Delete.Objects.push({ Key })
|
|
246
|
+
})
|
|
247
|
+
logger.info(deleteParams, '[S3] Deleting objects')
|
|
248
|
+
const deletedResp = await S3Client.deleteObjects(deleteParams)
|
|
249
|
+
if (deletedResp.Errors?.length) {
|
|
250
|
+
logger.error(deletedResp.Errors, '[S3] Error during deleteObjectsByPrefix')
|
|
251
|
+
}
|
|
252
|
+
if (objects.IsTruncated) {
|
|
253
|
+
await deleteObjectsByPrefix(Bucket, Prefix, objects.NextContinuationToken)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Ensures a bucket exists, creating it if it doesn't
|
|
259
|
+
*/
|
|
260
|
+
export const ensureBucketExists = async (Bucket: string): Promise<void> => {
|
|
261
|
+
try {
|
|
262
|
+
await S3Client.send(new HeadBucketCommand({ Bucket }))
|
|
263
|
+
// Bucket exists
|
|
264
|
+
return
|
|
265
|
+
} catch (error: any) {
|
|
266
|
+
// If bucket doesn't exist, create it
|
|
267
|
+
if (error.$metadata?.httpStatusCode === 404) {
|
|
268
|
+
try {
|
|
269
|
+
await S3Client.send(new CreateBucketCommand({ Bucket }))
|
|
270
|
+
logger.info({ Bucket }, '[S3] Created bucket')
|
|
271
|
+
} catch (createError: any) {
|
|
272
|
+
// Ignore if bucket was created by another process
|
|
273
|
+
if (createError.$metadata?.httpStatusCode !== 409) {
|
|
274
|
+
logger.error({ Bucket, error: createError }, '[S3] Failed to create bucket')
|
|
275
|
+
throw createError
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
// Some other error (permissions, etc.)
|
|
280
|
+
logger.error({ Bucket, error }, '[S3] Failed to check bucket existence')
|
|
281
|
+
throw error
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|