Skip to content

Commit

Permalink
Merge pull request #5 from zirkelc/s3-key
Browse files Browse the repository at this point in the history
feat: s3 dynamic keys
  • Loading branch information
zirkelc authored Oct 11, 2024
2 parents 7dfbfbe + dde38cd commit edf0b5a
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 120 deletions.
5 changes: 5 additions & 0 deletions .changeset/modern-pets-decide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"middy-store-s3": patch
---

feat: allow dynamic keys from payload
8 changes: 6 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
"typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit"
// "source.organizeImports.biome": "explicit"
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"editor.defaultFormatter": "biomejs.biome",
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/1.5.3/schema.json",
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"vcs": {
"clientKind": "git",
"enabled": true,
Expand Down
7 changes: 5 additions & 2 deletions examples/s3-store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { S3Store } from "middy-store-s3";
const context = {} as Context;

type Payload = {
id: string;
random: string;
};

Expand All @@ -44,13 +45,13 @@ try {
);
}

const s3Store = new S3Store({
const s3Store = new S3Store<Payload>({
/* Config is optional */
config: { region },
/* Bucket is required */
bucket,
/* Key is optional and defaults to randomUUID() */
key: () => randomUUID(),
key: ({ payload }) => payload.id,
});

const handler1 = middy()
Expand All @@ -61,6 +62,8 @@ const handler1 = middy()
)
.handler(async (input) => {
return {
/* Generate a random ID to be used as the key in S3 */
id: randomUUID(),
/* Generate a random payload of size 512kb */
random: randomBytes(Sizes.kb(512) /* 512kb */).toString("hex"),
};
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"changeset:publish": "changeset publish"
},
"devDependencies": {
"@biomejs/biome": "^1.8.3",
"@biomejs/biome": "^1.9.3",
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.7",
"@changesets/get-github-info": "^0.6.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/store-s3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const handler = middy()
new S3Store({
config: { region: "us-east-1" },
bucket: "bucket",
key: () => randomUUID(),
key: ({ payload }) => randomUUID(),
format: "arn",
}),
],
Expand Down
63 changes: 51 additions & 12 deletions packages/store-s3/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,35 +34,74 @@ export interface S3ObjectReference {
region?: string;
}

export interface S3StoreOptions {
type S3KeyArgs<TPayload> = { payload: TPayload };

/**
* The options for the `S3Store`.
*/
export interface S3StoreOptions<TPayload = unknown> {
/**
* The S3 client configuration.
* Can be a static object or a function that returns the configuration.
*/
config?: S3ClientConfig | (() => S3ClientConfig);
/**
* The name of the bucket to store the payload in.
* Can be a static string or a function that returns the bucket name.
*/
bucket: string | (() => string);
key?: string | (() => string);
/**
* The key to store the payload in the bucket.
* Can be a static string or a function that receives the payload as an argument and returns a string.
* Defaults to `randomUUID()`.
*
* @example
* ```typescript
* {
* key: ({ payload }) => `${payload.id}`
* }
* ```
*/
key?: string | ((args: S3KeyArgs<TPayload>) => string);
/**
* The format of the S3 reference.
* Defaults to the `url-s3-global-path` format: `s3://<bucket>/<...keys>`.
*/
format?: S3ReferenceFormat;
/**
* The maximum payload size in bytes that can be stored in S3.
* Defaults to `Infinity`.
*/
maxSize?: number;
/**
* The logger function to use for logging.
* Defaults to no logging.
*/
logger?: Logger;
}

export const STORE_NAME = "s3" as const;

export class S3Store implements StoreInterface<unknown, S3Reference> {
export class S3Store<TPayload = unknown>
implements StoreInterface<TPayload, S3Reference>
{
readonly name = STORE_NAME;

#config: () => S3ClientConfig;
#bucket: () => string;
#key: () => string;
#key: (args: S3KeyArgs<TPayload>) => string;
#logger: (...args: any[]) => void;
#maxSize: number;
#format: S3ReferenceFormat;

constructor(opts: S3StoreOptions) {
constructor(opts: S3StoreOptions<TPayload>) {
this.#maxSize = opts.maxSize ?? Sizes.INFINITY;
this.#logger = opts.logger ?? (() => {});
this.#format = opts.format ?? "url-s3-global-path";

this.#config = resolvableFn(opts.config ?? {});
this.#bucket = resolvableFn(opts.bucket);
this.#key = resolvableFn(opts.key ?? randomUUID);
this.#key = resolvableFn(opts.key ?? (() => randomUUID()));
}

canLoad(args: LoadArgs<unknown>): boolean {
Expand All @@ -84,7 +123,7 @@ export class S3Store implements StoreInterface<unknown, S3Reference> {
}
}

async load(args: LoadArgs<S3Reference>): Promise<unknown> {
async load(args: LoadArgs<S3Reference>): Promise<TPayload> {
this.#logger("Loading payload");

const client = this.getClient();
Expand All @@ -101,7 +140,7 @@ export class S3Store implements StoreInterface<unknown, S3Reference> {

this.#logger(`Loaded payload from bucket ${bucket} and key ${key}`);

return payload;
return payload as TPayload;
}

canStore(args: StoreArgs<unknown>): boolean {
Expand All @@ -120,12 +159,12 @@ export class S3Store implements StoreInterface<unknown, S3Reference> {
return true;
}

public async store(args: StoreArgs<unknown>): Promise<S3Reference> {
public async store(args: StoreArgs<TPayload>): Promise<S3Reference> {
this.#logger("Storing payload");

const { payload } = args;
const bucket = this.getBucket();
const key = this.getKey();
const key = this.getKey({ payload });
const client = this.getClient();

this.#logger(`Storing payload to bucket ${bucket} and key ${key}`);
Expand Down Expand Up @@ -165,8 +204,8 @@ export class S3Store implements StoreInterface<unknown, S3Reference> {
return bucket;
}

private getKey(): string {
const key = this.#key();
private getKey(args: S3KeyArgs<TPayload>): string {
const key = this.#key(args);
if (!key) {
this.#logger("Invalid key", { key });
throw new Error(`Invalid key: ${key}`);
Expand Down
1 change: 0 additions & 1 deletion packages/store-s3/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { S3Client } from "@aws-sdk/client-s3";
import {
type S3Object,
type S3UrlFormat,
Expand Down
Loading

0 comments on commit edf0b5a

Please sign in to comment.