@contract-kit/provider-storage-s3 1.0.0

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 ADDED
@@ -0,0 +1,28 @@
1
+ # @contract-kit/provider-storage-s3
2
+
3
+ ## 1.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 408ceb9: Add an S3-compatible storage provider with `createS3Storage`,
8
+ `createS3StorageProvider`, and `s3StorageProvider` for AWS S3, Cloudflare R2,
9
+ MinIO, and similar object stores.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [55f651c]
14
+ - Updated dependencies [e73a554]
15
+ - Updated dependencies [481e8ca]
16
+ - Updated dependencies [58d5528]
17
+ - Updated dependencies [c8b51ea]
18
+ - Updated dependencies [c5829fb]
19
+ - Updated dependencies [56ac09f]
20
+ - Updated dependencies [01eccbc]
21
+ - @contract-kit/devtools@1.0.0
22
+ - @contract-kit/ports@1.0.0
23
+
24
+ ## 0.1.10
25
+
26
+ ### Patch Changes
27
+
28
+ - Initial S3-compatible storage provider package.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # @contract-kit/provider-storage-s3
2
+
3
+ S3-compatible object storage provider for Contract Kit.
4
+
5
+ The provider installs the app-facing `ctx.ports.storage` port. Use it for
6
+ production object storage on AWS S3, Cloudflare R2, MinIO, Backblaze B2,
7
+ DigitalOcean Spaces, or another S3-compatible backend.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ bun add @contract-kit/provider-storage-s3
13
+ ```
14
+
15
+ ## Provider setup
16
+
17
+ ```typescript
18
+ import { s3StorageProvider } from "@contract-kit/provider-storage-s3";
19
+ import { createServer } from "@contract-kit/server";
20
+
21
+ const server = await createServer({
22
+ ports: basePorts,
23
+ providers: [s3StorageProvider],
24
+ createContext: ({ ports }) => ({ ports }),
25
+ routes,
26
+ });
27
+ ```
28
+
29
+ Environment variables:
30
+
31
+ | Variable | Description |
32
+ | --- | --- |
33
+ | `STORAGE_S3_BUCKET` | Bucket name. |
34
+ | `STORAGE_S3_REGION` | Region. Defaults to `us-east-1`. Use `auto` for Cloudflare R2. |
35
+ | `STORAGE_S3_ENDPOINT` | Optional S3-compatible endpoint. Required for R2, MinIO, Spaces, B2, and similar services. |
36
+ | `STORAGE_S3_ACCESS_KEY_ID` | Optional static access key. |
37
+ | `STORAGE_S3_SECRET_ACCESS_KEY` | Optional static secret key. |
38
+ | `STORAGE_S3_SESSION_TOKEN` | Optional static session token. |
39
+ | `STORAGE_S3_PUBLIC_BASE_URL` | Optional base URL returned by `publicUrl(...)` for public objects. |
40
+ | `STORAGE_S3_FORCE_PATH_STYLE` | Optional `true` or `false` path-style addressing toggle. |
41
+ | `STORAGE_S3_KEY_PREFIX` | Optional prefix for every object key written by this app. |
42
+
43
+ ## AWS S3
44
+
45
+ ```bash
46
+ STORAGE_S3_BUCKET=my-app-assets
47
+ STORAGE_S3_REGION=us-east-1
48
+ STORAGE_S3_PUBLIC_BASE_URL=https://cdn.example.com
49
+ ```
50
+
51
+ When credentials are omitted, the AWS SDK uses its normal credential provider
52
+ chain.
53
+
54
+ ## Cloudflare R2
55
+
56
+ ```bash
57
+ STORAGE_S3_BUCKET=my-app-assets
58
+ STORAGE_S3_REGION=auto
59
+ STORAGE_S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
60
+ STORAGE_S3_ACCESS_KEY_ID=...
61
+ STORAGE_S3_SECRET_ACCESS_KEY=...
62
+ STORAGE_S3_PUBLIC_BASE_URL=https://assets.example.com
63
+ ```
64
+
65
+ R2 is S3-compatible, but not every S3 feature exists on every compatible
66
+ service. This provider only relies on object `put`, `get`, `head`, and
67
+ `delete`.
68
+
69
+ ## Direct port factory
70
+
71
+ ```typescript
72
+ import { createS3Storage } from "@contract-kit/provider-storage-s3";
73
+
74
+ const storage = createS3Storage({
75
+ bucket: "my-app-assets",
76
+ region: "auto",
77
+ endpoint: "https://<account-id>.r2.cloudflarestorage.com",
78
+ credentials: {
79
+ accessKeyId: process.env.R2_ACCESS_KEY_ID!,
80
+ secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
81
+ },
82
+ publicBaseUrl: "https://assets.example.com",
83
+ });
84
+ ```
85
+
86
+ The same `StoragePort` works with local files, memory tests, and
87
+ S3-compatible object stores:
88
+
89
+ ```typescript
90
+ await ctx.ports.storage.put("avatars/user_123.png", avatarBytes, {
91
+ contentType: "image/png",
92
+ visibility: "public",
93
+ });
94
+
95
+ const object = await ctx.ports.storage.get("avatars/user_123.png");
96
+ const url = await ctx.ports.storage.publicUrl("avatars/user_123.png");
97
+ ```
98
+
99
+ ## Visibility
100
+
101
+ `visibility` is stored as reserved object metadata so `publicUrl(...)` can
102
+ return URLs only for objects written with `visibility: "public"`. The provider
103
+ does not set S3 ACLs. Configure bucket policies, R2 public buckets, or a CDN
104
+ outside the provider when objects should be publicly reachable.
105
+
106
+ The reserved metadata key is `ck-visibility`. It is hidden from
107
+ `StorageObject.metadata`.
108
+
109
+ ## Escape hatch
110
+
111
+ The provider also installs `ctx.ports.s3Storage` for S3-specific operations that
112
+ do not belong in `StoragePort`:
113
+
114
+ ```typescript
115
+ import { ListObjectsV2Command } from "@aws-sdk/client-s3";
116
+
117
+ const s3Key = ctx.ports.s3Storage.objectKey("exports/report.csv");
118
+ const s3Prefix = ctx.ports.s3Storage.objectPrefix("exports");
119
+
120
+ await ctx.ports.s3Storage.client.send(
121
+ new ListObjectsV2Command({
122
+ Bucket: ctx.ports.s3Storage.bucket,
123
+ Prefix: s3Prefix,
124
+ }),
125
+ );
126
+ ```
127
+
128
+ Use `objectKey(...)` when direct S3 calls need to address objects written
129
+ through `ctx.ports.storage`. Use `objectPrefix(...)` for list operations. Both
130
+ helpers apply the configured `STORAGE_S3_KEY_PREFIX`.
131
+
132
+ ## Devtools
133
+
134
+ When `ctx.ports.devtools` is installed, the provider records storage
135
+ operations under the `storage` watcher. Events include operation name, key,
136
+ bucket, duration, object size, visibility, and whether a lookup hit. Object
137
+ bodies and metadata values are never recorded.
138
+
139
+ ## License
140
+
141
+ MIT
@@ -0,0 +1,148 @@
1
+ import { S3Client, type S3ClientConfig } from "@aws-sdk/client-s3";
2
+ import { type StoragePort } from "@contract-kit/ports";
3
+ import { z } from "zod";
4
+ export type { StorageBody, StorageMetadata, StorageObject, StorageObjectBody, StoragePort, StorageVisibility, } from "@contract-kit/ports";
5
+ declare const S3StorageConfigSchema: z.ZodObject<{
6
+ BUCKET: z.ZodString;
7
+ REGION: z.ZodDefault<z.ZodString>;
8
+ ENDPOINT: z.ZodOptional<z.ZodString>;
9
+ ACCESS_KEY_ID: z.ZodOptional<z.ZodString>;
10
+ SECRET_ACCESS_KEY: z.ZodOptional<z.ZodString>;
11
+ SESSION_TOKEN: z.ZodOptional<z.ZodString>;
12
+ PUBLIC_BASE_URL: z.ZodOptional<z.ZodString>;
13
+ FORCE_PATH_STYLE: z.ZodOptional<z.ZodPipe<z.ZodEnum<{
14
+ true: "true";
15
+ false: "false";
16
+ }>, z.ZodTransform<boolean, "true" | "false">>>;
17
+ KEY_PREFIX: z.ZodOptional<z.ZodString>;
18
+ }, z.core.$strip>;
19
+ export type S3StorageConfig = z.infer<typeof S3StorageConfigSchema>;
20
+ export type S3StorageClient = Pick<S3Client, "send">;
21
+ export interface S3StorageOptions {
22
+ /**
23
+ * S3 bucket name.
24
+ */
25
+ bucket: string;
26
+ /**
27
+ * S3-compatible client. Omit to create an AWS SDK S3Client from config.
28
+ */
29
+ client?: S3StorageClient;
30
+ /**
31
+ * Region used when creating the default S3Client.
32
+ *
33
+ * @default "us-east-1"
34
+ */
35
+ region?: string;
36
+ /**
37
+ * S3-compatible endpoint. Required for R2, MinIO, Spaces, B2, and most
38
+ * non-AWS object stores.
39
+ */
40
+ endpoint?: string;
41
+ /**
42
+ * Static credentials used when creating the default S3Client.
43
+ */
44
+ credentials?: {
45
+ accessKeyId: string;
46
+ secretAccessKey: string;
47
+ sessionToken?: string;
48
+ };
49
+ /**
50
+ * Use path-style bucket addressing when required by the object store.
51
+ */
52
+ forcePathStyle?: boolean;
53
+ /**
54
+ * Prefix all object keys before sending them to S3.
55
+ */
56
+ keyPrefix?: string;
57
+ /**
58
+ * Base URL used by `publicUrl(...)` for public objects.
59
+ */
60
+ publicBaseUrl?: string;
61
+ /**
62
+ * Additional AWS SDK S3Client config.
63
+ */
64
+ clientConfig?: Omit<S3ClientConfig, "region" | "endpoint" | "credentials" | "forcePathStyle">;
65
+ /**
66
+ * Optional devtools target. The provider passes existing app ports here
67
+ * automatically; direct factory users can pass a devtools port explicitly.
68
+ */
69
+ devtools?: unknown;
70
+ }
71
+ export interface S3StorageProviderOptions extends Omit<S3StorageOptions, "bucket" | "client" | "devtools"> {
72
+ /**
73
+ * Provider name. Defaults to "storage-s3".
74
+ */
75
+ name?: string;
76
+ /**
77
+ * Default bucket used when STORAGE_S3_BUCKET is not set.
78
+ */
79
+ bucket?: string;
80
+ /**
81
+ * Optional client factory for tests or custom S3 clients.
82
+ */
83
+ createClient?: (config: S3StorageConfig) => S3StorageClient;
84
+ }
85
+ export declare function createS3Storage(options: S3StorageOptions): StoragePort;
86
+ export interface S3StorageProviderPorts {
87
+ storage: StoragePort;
88
+ s3Storage: {
89
+ client: S3StorageClient;
90
+ bucket: string;
91
+ keyPrefix: string;
92
+ objectKey(key: string): string;
93
+ objectPrefix(prefix: string): string;
94
+ };
95
+ }
96
+ export declare function createS3StorageProvider(options?: S3StorageProviderOptions): import("@contract-kit/ports").ServiceProvider<unknown, z.ZodObject<{
97
+ ACCESS_KEY_ID: z.ZodOptional<z.ZodString>;
98
+ SECRET_ACCESS_KEY: z.ZodOptional<z.ZodString>;
99
+ SESSION_TOKEN: z.ZodOptional<z.ZodString>;
100
+ BUCKET: z.ZodString | z.ZodDefault<z.ZodString>;
101
+ REGION: z.ZodDefault<z.ZodString>;
102
+ ENDPOINT: z.ZodDefault<z.ZodString> | z.ZodOptional<z.ZodString>;
103
+ PUBLIC_BASE_URL: z.ZodDefault<z.ZodString> | z.ZodOptional<z.ZodString>;
104
+ FORCE_PATH_STYLE: z.ZodOptional<z.ZodPipe<z.ZodEnum<{
105
+ true: "true";
106
+ false: "false";
107
+ }>, z.ZodTransform<boolean, "true" | "false">>> | z.ZodDefault<z.ZodPipe<z.ZodEnum<{
108
+ true: "true";
109
+ false: "false";
110
+ }>, z.ZodTransform<boolean, "true" | "false">>>;
111
+ KEY_PREFIX: z.ZodDefault<z.ZodString> | z.ZodOptional<z.ZodString>;
112
+ }, z.core.$strip>, {
113
+ storage: StoragePort;
114
+ s3Storage: {
115
+ client: S3StorageClient;
116
+ bucket: string;
117
+ keyPrefix: string;
118
+ objectKey(key: string): string;
119
+ objectPrefix(prefix: string): string;
120
+ };
121
+ }>;
122
+ export declare const s3StorageProvider: import("@contract-kit/ports").ServiceProvider<unknown, z.ZodObject<{
123
+ ACCESS_KEY_ID: z.ZodOptional<z.ZodString>;
124
+ SECRET_ACCESS_KEY: z.ZodOptional<z.ZodString>;
125
+ SESSION_TOKEN: z.ZodOptional<z.ZodString>;
126
+ BUCKET: z.ZodString | z.ZodDefault<z.ZodString>;
127
+ REGION: z.ZodDefault<z.ZodString>;
128
+ ENDPOINT: z.ZodDefault<z.ZodString> | z.ZodOptional<z.ZodString>;
129
+ PUBLIC_BASE_URL: z.ZodDefault<z.ZodString> | z.ZodOptional<z.ZodString>;
130
+ FORCE_PATH_STYLE: z.ZodOptional<z.ZodPipe<z.ZodEnum<{
131
+ true: "true";
132
+ false: "false";
133
+ }>, z.ZodTransform<boolean, "true" | "false">>> | z.ZodDefault<z.ZodPipe<z.ZodEnum<{
134
+ true: "true";
135
+ false: "false";
136
+ }>, z.ZodTransform<boolean, "true" | "false">>>;
137
+ KEY_PREFIX: z.ZodDefault<z.ZodString> | z.ZodOptional<z.ZodString>;
138
+ }, z.core.$strip>, {
139
+ storage: StoragePort;
140
+ s3Storage: {
141
+ client: S3StorageClient;
142
+ bucket: string;
143
+ keyPrefix: string;
144
+ objectKey(key: string): string;
145
+ objectPrefix(prefix: string): string;
146
+ };
147
+ }>;
148
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,QAAQ,EACR,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAML,KAAK,WAAW,EAEjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,YAAY,EACV,WAAW,EACX,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAQ7B,QAAA,MAAM,qBAAqB;;;;;;;;;;;;;iBAUzB,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IAEzB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,WAAW,CAAC,EAAE;QACZ,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IAEF;;OAEG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,YAAY,CAAC,EAAE,IAAI,CACjB,cAAc,EACd,QAAQ,GAAG,UAAU,GAAG,aAAa,GAAG,gBAAgB,CACzD,CAAC;IAEF;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,wBACf,SAAQ,IAAI,CAAC,gBAAgB,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;IAChE;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,eAAe,CAAC;CAC7D;AAuWD,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,WAAW,CAgTtE;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,EAAE;QACT,MAAM,EAAE,eAAe,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;QAC/B,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;KACtC,CAAC;CACH;AAED,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B;;;;;;;;;;;;;;;;;;;;;;;;;GA+FvC;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;EAA4B,CAAC"}