@kutur-kit/assets 0.0.0-stage → 0.1.0-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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -2
  3. package/dist/Asset.d.ts +174 -0
  4. package/dist/Asset.d.ts.map +1 -0
  5. package/dist/Asset.js +156 -0
  6. package/dist/Asset.js.map +1 -0
  7. package/dist/AssetBucket.d.ts +73 -0
  8. package/dist/AssetBucket.d.ts.map +1 -0
  9. package/dist/AssetBucket.js +19 -0
  10. package/dist/AssetBucket.js.map +1 -0
  11. package/dist/AssetDelivery.d.ts +23 -0
  12. package/dist/AssetDelivery.d.ts.map +1 -0
  13. package/dist/AssetDelivery.js +12 -0
  14. package/dist/AssetDelivery.js.map +1 -0
  15. package/dist/AssetSigning.d.ts +109 -0
  16. package/dist/AssetSigning.d.ts.map +1 -0
  17. package/dist/AssetSigning.js +93 -0
  18. package/dist/AssetSigning.js.map +1 -0
  19. package/dist/alchemy/r2.d.ts +40 -0
  20. package/dist/alchemy/r2.d.ts.map +1 -0
  21. package/dist/alchemy/r2.js +83 -0
  22. package/dist/alchemy/r2.js.map +1 -0
  23. package/dist/delivery/local.d.ts +17 -0
  24. package/dist/delivery/local.d.ts.map +1 -0
  25. package/dist/delivery/local.js +20 -0
  26. package/dist/delivery/local.js.map +1 -0
  27. package/dist/filesystem.d.ts +15 -0
  28. package/dist/filesystem.d.ts.map +1 -0
  29. package/dist/filesystem.js +90 -0
  30. package/dist/filesystem.js.map +1 -0
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +3 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/internal/bytes.d.ts +3 -0
  36. package/dist/internal/bytes.d.ts.map +1 -0
  37. package/dist/internal/bytes.js +11 -0
  38. package/dist/internal/bytes.js.map +1 -0
  39. package/dist/internal/http.d.ts +17 -0
  40. package/dist/internal/http.d.ts.map +1 -0
  41. package/dist/internal/http.js +38 -0
  42. package/dist/internal/http.js.map +1 -0
  43. package/dist/internal/sigv4.d.ts +15 -0
  44. package/dist/internal/sigv4.d.ts.map +1 -0
  45. package/dist/internal/sigv4.js +52 -0
  46. package/dist/internal/sigv4.js.map +1 -0
  47. package/dist/internal/url.d.ts +3 -0
  48. package/dist/internal/url.d.ts.map +1 -0
  49. package/dist/internal/url.js +3 -0
  50. package/dist/internal/url.js.map +1 -0
  51. package/dist/memory.d.ts +11 -0
  52. package/dist/memory.d.ts.map +1 -0
  53. package/dist/memory.js +34 -0
  54. package/dist/memory.js.map +1 -0
  55. package/dist/s3.d.ts +42 -0
  56. package/dist/s3.d.ts.map +1 -0
  57. package/dist/s3.js +67 -0
  58. package/dist/s3.js.map +1 -0
  59. package/dist/signing/local.d.ts +27 -0
  60. package/dist/signing/local.d.ts.map +1 -0
  61. package/dist/signing/local.js +128 -0
  62. package/dist/signing/local.js.map +1 -0
  63. package/dist/testing/AssetBucketContract.d.ts +18 -0
  64. package/dist/testing/AssetBucketContract.d.ts.map +1 -0
  65. package/dist/testing/AssetBucketContract.js +124 -0
  66. package/dist/testing/AssetBucketContract.js.map +1 -0
  67. package/dist/testing/AssetSigningContract.d.ts +18 -0
  68. package/dist/testing/AssetSigningContract.d.ts.map +1 -0
  69. package/dist/testing/AssetSigningContract.js +132 -0
  70. package/dist/testing/AssetSigningContract.js.map +1 -0
  71. package/dist/testing/index.d.ts +3 -0
  72. package/dist/testing/index.d.ts.map +1 -0
  73. package/dist/testing/index.js +3 -0
  74. package/dist/testing/index.js.map +1 -0
  75. package/package.json +109 -3
  76. package/src/Asset.ts +443 -0
  77. package/src/AssetBucket.ts +82 -0
  78. package/src/AssetDelivery.ts +41 -0
  79. package/src/AssetSigning.ts +206 -0
  80. package/src/alchemy/r2.ts +149 -0
  81. package/src/delivery/local.ts +41 -0
  82. package/src/filesystem.ts +166 -0
  83. package/src/index.ts +3 -0
  84. package/src/internal/bytes.ts +12 -0
  85. package/src/internal/http.ts +54 -0
  86. package/src/internal/sigv4.ts +84 -0
  87. package/src/internal/url.ts +2 -0
  88. package/src/memory.ts +65 -0
  89. package/src/s3.ts +138 -0
  90. package/src/signing/local.ts +210 -0
  91. package/src/testing/AssetBucketContract.ts +215 -0
  92. package/src/testing/AssetSigningContract.ts +254 -0
  93. package/src/testing/index.ts +3 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kutur Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,162 @@
1
- # Temporary Holding Version
1
+ # @kutur-kit/assets
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Typed file storage for Effect. An asset declares its bucket, its keys, the content types it accepts and its maximum size. Storage is a Layer per bucket: memory, a directory, S3-compatible storage or an R2 bucket through [alchemy](https://alchemy.run). Clients can upload and download through signed URLs, and objects of public buckets get URLs under a CDN or public domain.
4
+
5
+ Tested with Effect 4.1.2.
6
+
7
+ ```sh
8
+ pnpm add @kutur-kit/assets@alpha effect
9
+ ```
10
+
11
+ ## Assets
12
+
13
+ A bucket is a service. Its id names it in errors; the physical bucket is chosen by the Layer that provides it:
14
+
15
+ ```ts
16
+ import { Asset, AssetBucket } from "@kutur-kit/assets";
17
+
18
+ export class Avatars extends AssetBucket.Service<Avatars>()("avatars") {}
19
+
20
+ export const Avatar = Asset.make("avatar", {
21
+ bucket: Avatars,
22
+ key: AvatarId,
23
+ contentTypes: ["image/png", "image/jpeg", "image/webp"],
24
+ maxSize: "2 MiB",
25
+ cacheControl: "public, max-age=31536000, immutable",
26
+ });
27
+ ```
28
+
29
+ | Option | Meaning |
30
+ | -------------- | --------------------------------------------------------------------------------------------------------- |
31
+ | `bucket` | The bucket service. |
32
+ | `key` | A schema from the keys callers pass, such as a branded id, to a string. `Asset.Key` by default. |
33
+ | `contentTypes` | Accepted media types, lowercase and without parameters. Required: there is no default. |
34
+ | `maxSize` | A number of bytes or a string such as `"50 MiB"`. |
35
+ | `cacheControl` | The `Cache-Control` objects are stored and served with. `no-cache` by default. CDN rules can override it. |
36
+
37
+ Objects are stored under `<id>/<key>`, so several assets can share a bucket. Keys are checked before any request. Each segment uses S3's safe characters (letters, digits and `! - _ . ' ( )`), is not `.` or `..` and has at most 255 characters. A key has at most 1024 characters.
38
+
39
+ ```ts
40
+ const program = Effect.gen(function* () {
41
+ const info = yield* Avatar.put(avatarId, body, { contentType: "image/png", contentLength });
42
+ const object = yield* Avatar.get(avatarId); // Option of { info, body }
43
+
44
+ yield* Avatar.delete(avatarId);
45
+ });
46
+ ```
47
+
48
+ `put` streams the body and fails with `Asset.TooLarge` as soon as it exceeds `maxSize`, with `Asset.ContentLengthMismatch` when it differs from the declared length and with `Asset.UnsupportedContentType` before reading it. A failed body leaves the previous object in place. `Asset.ContentType` and `Asset.Key` decode untrusted request values, such as headers and path parameters, to the types the asset accepts.
49
+
50
+ `get` and `head` fail with `Asset.UnexpectedContentType` for an object the asset does not accept, such as one written by other code.
51
+
52
+ ## Storage
53
+
54
+ | Module | Backs a bucket with |
55
+ | ------------------------------ | --------------------------------------------------------------- |
56
+ | `@kutur-kit/assets/memory` | Memory, for tests. |
57
+ | `@kutur-kit/assets/filesystem` | Files in a directory, for development. |
58
+ | `@kutur-kit/assets/s3` | S3 or a store with its API, such as R2 or RustFS. |
59
+ | `@kutur-kit/assets/alchemy/r2` | An R2 bucket declared with alchemy, through its Worker binding. |
60
+
61
+ ```ts
62
+ import * as S3Assets from "@kutur-kit/assets/s3";
63
+
64
+ export const AvatarsLive = S3Assets.layer(Avatars, { bucket: "avatars" });
65
+ ```
66
+
67
+ The S3 layer needs `Credentials` and an `HttpClient` from `@distilled.cloud/aws`, and an `Endpoint` for stores other than AWS. For R2, `S3Assets.r2Endpoint(accountId)` gives the endpoint; sign requests for the region `auto`.
68
+
69
+ ## Signed uploads and downloads
70
+
71
+ Large files go from the client to storage directly. `AssetSigning.Upload(asset)` decodes what a client declares, and `presignPut` signs a URL for exactly that:
72
+
73
+ ```ts
74
+ import * as AssetSigning from "@kutur-kit/assets/AssetSigning";
75
+
76
+ const NewFile = Schema.Struct({ ...AssetSigning.Upload(ProjectFile).fields, name: Schema.String });
77
+
78
+ const start = (upload: typeof NewFile.Type) =>
79
+ Effect.gen(function* () {
80
+ // A `SignedRequest`: the client sends its method, URL and headers as given.
81
+ return yield* AssetSigning.presignPut(ProjectFile, fileId, upload, { expiresIn: "15 minutes" });
82
+ });
83
+
84
+ const complete = (upload: typeof NewFile.Type) =>
85
+ Effect.gen(function* () {
86
+ // Fails with `AssetSigning.UploadNotConfirmed` unless the object arrived as declared.
87
+ yield* AssetSigning.confirm(ProjectFile, fileId, upload);
88
+ });
89
+ ```
90
+
91
+ A signed upload is bound to its key, content type, length and the asset's cache control, and creates an object only: storage refuses a different request, and refuses the URL once the object exists. Confirm before recording an upload as complete, since the client may never send it.
92
+
93
+ `presignGet` signs a download URL. Signing is a service per bucket:
94
+
95
+ | Module | Signs |
96
+ | --------------------------------- | -------------------------------------------------------------------- |
97
+ | `S3Assets.signing` | With SigV4, for S3-compatible storage. |
98
+ | `@kutur-kit/assets/alchemy/r2` | With the S3 credentials alchemy binds to a Worker for an R2 bucket. |
99
+ | `@kutur-kit/assets/signing/local` | With an HMAC, verified by its own routes, for development and tests. |
100
+
101
+ Browsers send a CORS preflight before a signed upload. Allow `AssetSigning.uploadHeaders` for `PUT` from your app's origin in the bucket's CORS rules; `AssetsR2.corsRule(origins)` builds that rule for alchemy.
102
+
103
+ ## Public delivery
104
+
105
+ `AssetDelivery.layer(bucket, { baseUrl })` maps keys to URLs under a CDN or public domain, and `AssetDelivery.url(asset, key)` returns one. `@kutur-kit/assets/delivery/local` serves a bucket from your app's server, as a public bucket would be served, with the stored cache control, `nosniff` and a sandboxing content security policy.
106
+
107
+ Give an object a new key when its content changes, such as a new id per upload, so it can be cached with `immutable`.
108
+
109
+ ## R2 with alchemy
110
+
111
+ ```ts
112
+ import * as Cloudflare from "@alchemy.run/cloudflare";
113
+ import * as AssetsR2 from "@kutur-kit/assets/alchemy/r2";
114
+
115
+ const Uploads = Cloudflare.R2.Bucket("Uploads", {
116
+ cors: [AssetsR2.corsRule(["https://app.example.com"])],
117
+ });
118
+
119
+ export default class Api extends Cloudflare.Worker<Api>()(
120
+ "Api",
121
+ { main: import.meta.url },
122
+ Effect.gen(function* () {
123
+ // ...
124
+ }).pipe(
125
+ Effect.provide(
126
+ Layer.mergeAll(AssetsR2.layer(Avatars, Uploads), AssetsR2.signing(Avatars, Uploads)).pipe(
127
+ Layer.provideMerge(Cloudflare.R2.ReadWriteBucketBinding),
128
+ ),
129
+ ),
130
+ ),
131
+ ) {}
132
+ ```
133
+
134
+ `layer` stores through alchemy's `ReadWriteBucket`: provide `ReadWriteBucketBinding` in a Worker or `ReadWriteBucketHttp` elsewhere. Content type and cache control are stored as the object's HTTP metadata.
135
+
136
+ ## Testing
137
+
138
+ `@kutur-kit/assets/testing` has contracts for other implementations: `AssetBucketContract.cases` for a bucket and `AssetSigningContract.cases` for signing and the storage it signs for. Each case is an Effect; run it with any test runner:
139
+
140
+ ```ts
141
+ import * as Memory from "@kutur-kit/assets/memory";
142
+ import { AssetBucketContract } from "@kutur-kit/assets/testing";
143
+
144
+ for (const { name, run } of AssetBucketContract.cases)
145
+ it.effect(name, () => run.pipe(Effect.provide(Memory.layer(AssetBucketContract.ContractBucket))));
146
+ ```
147
+
148
+ ## Requirements
149
+
150
+ - `effect` 4.1.2 or later 4.x.
151
+ - `@kutur-kit/assets/s3` needs `@distilled.cloud/aws` 1.0.0-rc.18.
152
+ - `@kutur-kit/assets/alchemy/r2` needs `alchemy` and `@alchemy.run/cloudflare` 2.0.0-beta.85.
153
+
154
+ ## Limitations
155
+
156
+ - The S3 and R2 layers collect a body before sending it, so uploads through the app are limited by memory. Larger files belong in signed uploads.
157
+ - `alchemy/r2` works only in code bundled by alchemy: its modules import packages that other bundlers, such as Wrangler's, cannot resolve. Its bucket operations fail if called while a Worker is constructed rather than while it handles a request.
158
+ - `AssetsR2.signing` deploys a bucket-scoped API token for the Worker, which needs a Cloudflare API token with the account permission API Tokens Write. OAuth sessions cannot create one.
159
+ - Signed URLs work until they expire, whoever holds them. Removing a user's access does not revoke URLs already handed out.
160
+ - `signing/local` checks that an object does not exist before writing it, so two uploads racing to one key may both succeed.
161
+ - The filesystem layer is atomic for one process only.
162
+ - A changed `cacheControl` applies to new objects; stored objects keep theirs.
@@ -0,0 +1,174 @@
1
+ import type * as AssetBucket from "./AssetBucket.ts";
2
+ import { ByteSize, Effect, Option, Schema, Stream } from "effect";
3
+ /**
4
+ * A media type without parameters, such as `image/png`. Only the top-level
5
+ * type is checked, which catches typos without a registry of every subtype.
6
+ */
7
+ export type MediaType = `${"application" | "audio" | "font" | "image" | "model" | "text" | "video"}/${string}`;
8
+ /**
9
+ * A key that fits every store and URL without encoding: segments separated by
10
+ * `/`, each made of letters, digits and `! - _ . ' ( )`, not `.` or `..`, at
11
+ * most 255 characters each and 1024 in all. The asset id prefix also counts towards the limit,
12
+ * which each asset's `Key` checks.
13
+ */
14
+ export declare const Key: Schema.brand<Schema.String, "@kutur-kit/assets/Key">;
15
+ export type Key = typeof Key.Type;
16
+ /** Encodes an asset's keys to strings. Its encoded keys are checked like `Key`. */
17
+ export type KeySchema<K> = Schema.Codec<K, string>;
18
+ export interface Definition<Type extends MediaType = MediaType, K = Key> {
19
+ /** A bucket from `AssetBucket.Service`. */
20
+ readonly bucket: AssetBucket.Any;
21
+ /**
22
+ * The keys callers pass, such as a `UserId` or a `Schema.TemplateLiteral`
23
+ * of ids. `Key` by default.
24
+ */
25
+ readonly key?: KeySchema<K>;
26
+ /**
27
+ * Accepted media types, in lowercase and without parameters. There is no
28
+ * default: a bucket served from your domain must not accept arbitrary types
29
+ * such as `text/html`.
30
+ */
31
+ readonly contentTypes: readonly [Type, ...ReadonlyArray<Type>];
32
+ /**
33
+ * Such as `"5 MiB"` or a number of bytes. Checked while the body streams,
34
+ * before the store commits it.
35
+ */
36
+ readonly maxSize: ByteSize.Input;
37
+ /**
38
+ * The `Cache-Control` objects are stored and served with, such as
39
+ * `public, max-age=31536000, immutable` when each upload gets a new key.
40
+ * Public buckets, such as R2's on a custom domain, send it to CDNs and
41
+ * browsers.
42
+ *
43
+ * `no-cache` by default. Without one, a CDN applies its own default TTL,
44
+ * and browsers send `no-cache` with signed uploads anyway: they add it to
45
+ * requests with `If-None-Match`, which makes those write-once.
46
+ *
47
+ * CDN rules can override it, such as Cloudflare Cache Rules set to
48
+ * override origin or a CloudFront minimum TTL. Changing it here applies to
49
+ * new objects only: stored ones keep theirs.
50
+ */
51
+ readonly cacheControl?: string | undefined;
52
+ }
53
+ /** The keys an asset definition takes: its key schema's type, or `Key`. */
54
+ export type KeyOf<D> = D extends {
55
+ readonly key: KeySchema<infer K>;
56
+ } ? K : Key;
57
+ export type ContentTypeOf<D extends Definition<MediaType, unknown>> = D["contentTypes"][number];
58
+ /** An id that is one key segment, or a message naming the problem. */
59
+ export type CheckId<Id extends string> = Id extends "" | "." | ".." | `${string}/${string}` ? "Asset ids must be one key segment, without `/` and not `.` or `..`" : Id;
60
+ /** A content type in lowercase without parameters, or a message naming the problem. */
61
+ export type CheckContentType<Type extends string> = Type extends `${string};${string}` | `${string} ${string}` ? "Asset content types must have no parameters, such as `; charset=utf-8`" : Type extends `${string}${UppercaseLetter}${string}` ? "Asset content types must be lowercase" : Type;
62
+ type UppercaseLetter = Uppercase<"a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z">;
63
+ declare const InvalidKey_base: Schema.Class<InvalidKey, Schema.TaggedStruct<"AssetInvalidKey", {
64
+ readonly asset: Schema.String;
65
+ readonly reason: Schema.String;
66
+ }>, import("effect/Cause").YieldableError & {
67
+ readonly brand: unique symbol;
68
+ }>;
69
+ export declare class InvalidKey extends InvalidKey_base {
70
+ }
71
+ declare const UnsupportedContentType_base: Schema.Class<UnsupportedContentType, Schema.TaggedStruct<"AssetUnsupportedContentType", {
72
+ readonly asset: Schema.String;
73
+ readonly contentType: Schema.String;
74
+ }>, import("effect/Cause").YieldableError & {
75
+ readonly brand: unique symbol;
76
+ }>;
77
+ export declare class UnsupportedContentType extends UnsupportedContentType_base {
78
+ }
79
+ declare const ContentLengthMismatch_base: Schema.Class<ContentLengthMismatch, Schema.TaggedStruct<"AssetContentLengthMismatch", {
80
+ readonly asset: Schema.String;
81
+ readonly contentLength: Schema.ByteSize;
82
+ }>, import("effect/Cause").YieldableError & {
83
+ readonly brand: unique symbol;
84
+ }>;
85
+ /** A body is longer or shorter than the `contentLength` declared for it. */
86
+ export declare class ContentLengthMismatch extends ContentLengthMismatch_base {
87
+ }
88
+ declare const TooLarge_base: Schema.Class<TooLarge, Schema.TaggedStruct<"AssetTooLarge", {
89
+ readonly asset: Schema.String;
90
+ readonly maxSize: Schema.ByteSize;
91
+ }>, import("effect/Cause").YieldableError & {
92
+ readonly brand: unique symbol;
93
+ }>;
94
+ export declare class TooLarge extends TooLarge_base {
95
+ }
96
+ declare const UnexpectedContentType_base: Schema.Class<UnexpectedContentType, Schema.TaggedStruct<"AssetUnexpectedContentType", {
97
+ readonly asset: Schema.String;
98
+ /** The key in the store, with the asset id prefix. */
99
+ readonly key: Schema.String;
100
+ readonly contentType: Schema.String;
101
+ }>, import("effect/Cause").YieldableError & {
102
+ readonly brand: unique symbol;
103
+ }>;
104
+ /**
105
+ * A stored object's content type is not one the asset accepts, such as one
106
+ * written by other code or under an older definition.
107
+ */
108
+ export declare class UnexpectedContentType extends UnexpectedContentType_base {
109
+ }
110
+ export interface Info<Type extends MediaType> extends AssetBucket.Info {
111
+ readonly contentType: Type;
112
+ }
113
+ export interface StoredObject<Type extends MediaType> extends AssetBucket.StoredObject {
114
+ readonly info: Info<Type>;
115
+ }
116
+ export type ReadError = InvalidKey | UnexpectedContentType | AssetBucket.AssetBucketError;
117
+ /** What `toLayer` helpers need of an asset. */
118
+ export interface Any {
119
+ readonly id: string;
120
+ readonly bucket: AssetBucket.Any;
121
+ }
122
+ /** The bucket service an asset requires. */
123
+ export type BucketOf<A> = A extends {
124
+ readonly bucket: AssetBucket.Tag<infer B>;
125
+ } ? B : never;
126
+ export interface Asset<Id extends string, Type extends MediaType, K, B> {
127
+ readonly id: Id;
128
+ readonly bucket: AssetBucket.Tag<B>;
129
+ readonly contentTypes: ReadonlyArray<Type>;
130
+ readonly maxSize: ByteSize.ByteSize;
131
+ readonly cacheControl: string;
132
+ /**
133
+ * Decodes an untrusted key, such as a path parameter, to one the asset
134
+ * accepts, checking that the stored key is portable.
135
+ */
136
+ readonly Key: KeySchema<K>;
137
+ /**
138
+ * Decodes an untrusted content type, such as a request header, to one the
139
+ * asset accepts. Ignores case, whitespace and parameters such as `charset`.
140
+ */
141
+ readonly ContentType: Schema.Codec<Type, string>;
142
+ readonly put: <E>(key: K, body: Stream.Stream<Uint8Array, E>, options: {
143
+ readonly contentType: Type;
144
+ /**
145
+ * The body's length, such as a request's `Content-Length`. A length
146
+ * over `maxSize` fails before the body is read, and the bucket can
147
+ * stream a body of known length.
148
+ */
149
+ readonly contentLength?: ByteSize.ByteSize | undefined;
150
+ }) => Effect.Effect<Info<Type>, InvalidKey | UnsupportedContentType | TooLarge | ContentLengthMismatch | AssetBucket.AssetBucketError | E, B>;
151
+ readonly get: (key: K) => Effect.Effect<Option.Option<StoredObject<Type>>, ReadError, B>;
152
+ readonly head: (key: K) => Effect.Effect<Option.Option<Info<Type>>, ReadError, B>;
153
+ readonly delete: (key: K) => Effect.Effect<void, InvalidKey | AssetBucket.AssetBucketError, B>;
154
+ /** The key the object is stored under in the bucket: `<id>/<encoded key>`, checked. */
155
+ readonly storedKey: (key: K) => Effect.Effect<string, InvalidKey>;
156
+ /**
157
+ * Checks a content type and length as `put` does, for uploads that do not
158
+ * pass through `put`, such as to presigned URLs.
159
+ */
160
+ readonly check: (upload: {
161
+ readonly contentType: Type;
162
+ readonly contentLength: ByteSize.ByteSize;
163
+ }) => Effect.Effect<void, UnsupportedContentType | TooLarge>;
164
+ }
165
+ /**
166
+ * Defines a kind of asset. Objects are stored in the definition's bucket
167
+ * under `<id>/<key>`, so several kinds can share a bucket.
168
+ */
169
+ export declare const make: <const Id extends string, const D extends Definition<MediaType, unknown>, B>(checkedId: CheckId<Id>, definition: D & {
170
+ readonly bucket: AssetBucket.Tag<B>;
171
+ readonly contentTypes: ReadonlyArray<CheckContentType<ContentTypeOf<D>>>;
172
+ }) => Asset<Id, ContentTypeOf<D>, KeyOf<D>, B>;
173
+ export {};
174
+ //# sourceMappingURL=Asset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Asset.d.ts","sources":["../src/Asset.ts"],"names":[],"mappings":"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"}
package/dist/Asset.js ADDED
@@ -0,0 +1,156 @@
1
+ import { ByteSize, Effect, Option, Schema, SchemaGetter, Stream } from "effect";
2
+ // S3 and R2 limit keys to 1024 bytes, prefixes included. Keys are ASCII, so
3
+ // bytes are characters.
4
+ // https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html
5
+ // https://developers.cloudflare.com/r2/platform/limits/
6
+ const maxKeyLength = 1024;
7
+ // S3's safe characters, without `*`, which Windows file names forbid. Keys made
8
+ // of these need no encoding in URLs or file system paths. S3 also advises
9
+ // against `.` and `..` segments, which tools resolve as relative paths.
10
+ // https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html#object-key-guidelines
11
+ // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
12
+ const segmentPattern = /^[0-9A-Za-z!\-_.'()]+$/;
13
+ // Stores that keep objects as files, such as RustFS, refuse longer segments,
14
+ // as file systems limit names to 255 bytes.
15
+ const maxSegmentLength = 255;
16
+ const isSegment = (segment) => segmentPattern.test(segment) &&
17
+ segment.length <= maxSegmentLength &&
18
+ segment !== "." &&
19
+ segment !== "..";
20
+ const portable = (prefix) => Schema.makeFilter((key) => {
21
+ if (!key.split("/").every(isSegment))
22
+ return "segments must be non-empty, not `.` or `..`, at most 255 characters, and use only letters, digits and ! - _ . ' ( )";
23
+ if (prefix.length + key.length > maxKeyLength)
24
+ return `${prefix === "" ? "keys" : `keys with the prefix ${prefix}`} must fit in ${maxKeyLength} characters`;
25
+ return undefined;
26
+ });
27
+ /**
28
+ * A key that fits every store and URL without encoding: segments separated by
29
+ * `/`, each made of letters, digits and `! - _ . ' ( )`, not `.` or `..`, at
30
+ * most 255 characters each and 1024 in all. The asset id prefix also counts towards the limit,
31
+ * which each asset's `Key` checks.
32
+ */
33
+ export const Key = Schema.String.check(portable("")).pipe(Schema.brand("@kutur-kit/assets/Key"));
34
+ export class InvalidKey extends Schema.TaggedError()("AssetInvalidKey", { asset: Schema.String, reason: Schema.String }, { httpApiStatus: 400 }) {
35
+ }
36
+ export class UnsupportedContentType extends Schema.TaggedError()("AssetUnsupportedContentType", { asset: Schema.String, contentType: Schema.String }, { httpApiStatus: 415 }) {
37
+ }
38
+ /** A body is longer or shorter than the `contentLength` declared for it. */
39
+ export class ContentLengthMismatch extends Schema.TaggedError()("AssetContentLengthMismatch", { asset: Schema.String, contentLength: Schema.ByteSize }, { httpApiStatus: 400 }) {
40
+ }
41
+ export class TooLarge extends Schema.TaggedError()("AssetTooLarge", { asset: Schema.String, maxSize: Schema.ByteSize }, { httpApiStatus: 413 }) {
42
+ }
43
+ /**
44
+ * A stored object's content type is not one the asset accepts, such as one
45
+ * written by other code or under an older definition.
46
+ */
47
+ export class UnexpectedContentType extends Schema.TaggedError()("AssetUnexpectedContentType", {
48
+ asset: Schema.String,
49
+ /** The key in the store, with the asset id prefix. */
50
+ key: Schema.String,
51
+ contentType: Schema.String,
52
+ }) {
53
+ }
54
+ const essence = (contentType) => contentType.replace(/;.*/su, "").trim().toLowerCase();
55
+ /**
56
+ * Defines a kind of asset. Objects are stored in the definition's bucket
57
+ * under `<id>/<key>`, so several kinds can share a bucket.
58
+ */
59
+ export const make = (checkedId, definition) => {
60
+ // SAFETY: CheckId<Id> is Id itself for every id the check below accepts.
61
+ const id = checkedId;
62
+ if (!isSegment(id))
63
+ throw new Error(`Asset id must be a single key segment: ${JSON.stringify(id)}`);
64
+ const maxSize = Option.getOrThrowWith(ByteSize.fromInput(definition.maxSize), () => new Error(`Asset ${id} maxSize is not a byte size: ${String(definition.maxSize)}`));
65
+ for (const contentType of definition.contentTypes)
66
+ if (essence(contentType) !== contentType)
67
+ throw new Error(`Asset ${id} content types must be lowercase without parameters: ${JSON.stringify(contentType)}`);
68
+ const cacheControl = definition.cacheControl ?? "no-cache";
69
+ const accepted = new Set(definition.contentTypes);
70
+ const isAccepted = (contentType) => accepted.has(contentType);
71
+ const ContentType = Schema.String.pipe(Schema.decodeTo(Schema.String, {
72
+ decode: SchemaGetter.transform(essence),
73
+ encode: SchemaGetter.passthrough(),
74
+ }), Schema.decodeTo(Schema.Literals(definition.contentTypes)));
75
+ // SAFETY: KeyOf<D> is the type of the definition's key schema, or Key without one.
76
+ const keySchema = (definition.key ?? Key);
77
+ const AssetKey = Schema.String.check(portable(`${id}/`)).pipe(Schema.decodeTo(keySchema));
78
+ const encodeKey = Schema.encodeEffect(AssetKey);
79
+ const locate = (key) => encodeKey(key).pipe(Effect.mapError((error) => new InvalidKey({ asset: id, reason: error.message })), Effect.map((encoded) => `${id}/${encoded}`));
80
+ // Types can be bypassed, so `put` checks again what the compiler checked.
81
+ const accept = (contentType) => isAccepted(contentType)
82
+ ? Effect.void
83
+ : Effect.fail(new UnsupportedContentType({ asset: id, contentType }));
84
+ const expect = (key, info) => isAccepted(info.contentType)
85
+ ? Effect.succeed({ ...info, contentType: info.contentType })
86
+ : Effect.fail(new UnexpectedContentType({ asset: id, key, contentType: info.contentType }));
87
+ const declared = (contentLength) => contentLength !== undefined && contentLength > maxSize
88
+ ? Effect.fail(new TooLarge({ asset: id, maxSize }))
89
+ : Effect.void;
90
+ // Fails a body over maxSize or its declared length as soon as it is, and
91
+ // one shorter than its declared length at its end, before the bucket commits.
92
+ const measure = (body, contentLength) => Stream.suspend(() => {
93
+ let size = 0n;
94
+ const check = (chunk) => {
95
+ size += BigInt(chunk.length);
96
+ if (size > maxSize)
97
+ return Effect.fail(new TooLarge({ asset: id, maxSize }));
98
+ return contentLength !== undefined && size > contentLength
99
+ ? Effect.fail(new ContentLengthMismatch({ asset: id, contentLength }))
100
+ : Effect.succeed(chunk);
101
+ };
102
+ const counted = Stream.mapEffect(body, check);
103
+ if (contentLength === undefined)
104
+ return counted;
105
+ const complete = Effect.suspend(() => size === contentLength
106
+ ? Effect.void
107
+ : Effect.fail(new ContentLengthMismatch({ asset: id, contentLength })));
108
+ return Stream.concat(counted, Stream.fromEffectDrain(complete));
109
+ });
110
+ return {
111
+ id,
112
+ bucket: definition.bucket,
113
+ contentTypes: definition.contentTypes,
114
+ maxSize,
115
+ cacheControl,
116
+ Key: AssetKey,
117
+ ContentType,
118
+ put: (key, body, { contentType, contentLength }) => Effect.gen(function* () {
119
+ const stored = yield* locate(key);
120
+ yield* accept(contentType);
121
+ yield* declared(contentLength);
122
+ const bucket = yield* definition.bucket;
123
+ const info = yield* bucket.put({
124
+ key: stored,
125
+ contentType,
126
+ cacheControl,
127
+ body: measure(body, contentLength),
128
+ contentLength,
129
+ });
130
+ return { ...info, contentType };
131
+ }),
132
+ get: (key) => Effect.gen(function* () {
133
+ const stored = yield* locate(key);
134
+ const bucket = yield* definition.bucket;
135
+ const object = yield* bucket.get(stored);
136
+ if (Option.isNone(object))
137
+ return Option.none();
138
+ const info = yield* expect(stored, object.value.info);
139
+ return Option.some({ ...object.value, info });
140
+ }),
141
+ head: (key) => Effect.gen(function* () {
142
+ const stored = yield* locate(key);
143
+ const bucket = yield* definition.bucket;
144
+ const info = yield* bucket.head(stored);
145
+ return Option.isNone(info) ? Option.none() : Option.some(yield* expect(stored, info.value));
146
+ }),
147
+ delete: (key) => Effect.gen(function* () {
148
+ const stored = yield* locate(key);
149
+ const bucket = yield* definition.bucket;
150
+ yield* bucket.delete(stored);
151
+ }),
152
+ storedKey: locate,
153
+ check: ({ contentType, contentLength }) => Effect.andThen(accept(contentType), declared(contentLength)),
154
+ };
155
+ };
156
+ //# sourceMappingURL=Asset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Asset.js","sources":["../src/Asset.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { type ByteSize, Context, type Effect, type Option, Schema, type Stream } from "effect";
2
+ export interface Info {
3
+ readonly size: ByteSize.ByteSize;
4
+ readonly contentType: string;
5
+ /**
6
+ * The `Cache-Control` the object is served with, such as by a CDN in front
7
+ * of a public bucket. Absent when the object has none.
8
+ */
9
+ readonly cacheControl?: string | undefined;
10
+ }
11
+ export interface StoredObject {
12
+ readonly info: Info;
13
+ /** Implementations may stream from storage, so read it at most once. */
14
+ readonly body: Stream.Stream<Uint8Array, AssetBucketError>;
15
+ }
16
+ export interface PutInput<E> {
17
+ readonly key: string;
18
+ readonly contentType: string;
19
+ /** Stored with the object, to be served with it. */
20
+ readonly cacheControl?: string | undefined;
21
+ readonly body: Stream.Stream<Uint8Array, E>;
22
+ /**
23
+ * The body's exact length, when the caller knows it. Stores that need a
24
+ * length to stream, such as R2, would otherwise buffer the body.
25
+ */
26
+ readonly contentLength?: ByteSize.ByteSize | undefined;
27
+ }
28
+ declare const AssetBucketError_base: Schema.Class<AssetBucketError, Schema.TaggedStruct<"AssetBucketError", {
29
+ readonly operation: Schema.Literals<readonly ["put", "get", "head", "delete", "sign"]>;
30
+ readonly bucket: Schema.String;
31
+ readonly key: Schema.String;
32
+ readonly cause: Schema.Defect;
33
+ }>, import("effect/Cause").YieldableError & {
34
+ readonly brand: unique symbol;
35
+ }>;
36
+ export declare class AssetBucketError extends AssetBucketError_base {
37
+ }
38
+ /**
39
+ * Object storage for one bucket. Implementations must keep keys apart,
40
+ * including a key and the keys nested under it, replace an object on `put`,
41
+ * keep its content type and cache control,
42
+ * leave the previous object in place when a body fails and treat deleting a
43
+ * missing object as success.
44
+ */
45
+ export interface Bucket {
46
+ readonly put: <E>(input: PutInput<E>) => Effect.Effect<Info, AssetBucketError | E>;
47
+ readonly get: (key: string) => Effect.Effect<Option.Option<StoredObject>, AssetBucketError>;
48
+ readonly head: (key: string) => Effect.Effect<Option.Option<Info>, AssetBucketError>;
49
+ readonly delete: (key: string) => Effect.Effect<void, AssetBucketError>;
50
+ }
51
+ /** A logical bucket: assets name it, and a layer per environment backs it. */
52
+ export interface Tag<Self, Id extends string = string> extends Context.Service<Self, Bucket> {
53
+ new (_: never): {
54
+ readonly key: `@kutur-kit/assets/AssetBucket/${Id}`;
55
+ };
56
+ readonly id: Id;
57
+ }
58
+ /** What asset definitions need of a bucket. Its `Tag` adds the service type. */
59
+ export interface Any {
60
+ readonly id: string;
61
+ }
62
+ /**
63
+ * Declares a logical bucket as a service:
64
+ *
65
+ * ```ts
66
+ * export class Public extends AssetBucket.Service<Public>()("public") {}
67
+ * ```
68
+ *
69
+ * The id names the bucket in errors and adapters; it is not a physical bucket name.
70
+ */
71
+ export declare const Service: <Self>() => <const Id extends string>(id: Id) => Tag<Self, Id>;
72
+ export {};
73
+ //# sourceMappingURL=AssetBucket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssetBucket.d.ts","sources":["../src/AssetBucket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE/F,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAC5C;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,QAAQ,GAAG,SAAS,CAAC;CACxD;;;;;;;oBAImB,OAAO,MAAM;;AAFjC,qBAAa,gBAAiB,SAAQ,qBAQpC;CAAG;AAEL;;;;;;GAMG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,GAAG,CAAC,CAAC,CAAC;IACnF,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC,CAAC;IAC5F,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACrF,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;CACzE;AAED,8EAA8E;AAC9E,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,EAAE,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;IAI1F,KAAK,CAAC,EAAE,KAAK,GAAG;QAAE,QAAQ,CAAC,GAAG,EAAE,iCAAiC,EAAE,EAAE,CAAA;KAAE,CAAC;IACxE,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;CACjB;AAED,gFAAgF;AAChF,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GACjB,IAAI,QACJ,KAAK,CAAC,EAAE,SAAS,MAAM,MAAM,EAAE,KAAG,GAAG,CAAC,IAAI,EAAE,EAAE,CACgD,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { Context, Schema } from "effect";
2
+ export class AssetBucketError extends Schema.TaggedError()("AssetBucketError", {
3
+ operation: Schema.Literals(["put", "get", "head", "delete", "sign"]),
4
+ bucket: Schema.String,
5
+ key: Schema.String,
6
+ cause: Schema.Defect(),
7
+ }) {
8
+ }
9
+ /**
10
+ * Declares a logical bucket as a service:
11
+ *
12
+ * ```ts
13
+ * export class Public extends AssetBucket.Service<Public>()("public") {}
14
+ * ```
15
+ *
16
+ * The id names the bucket in errors and adapters; it is not a physical bucket name.
17
+ */
18
+ export const Service = () => (id) => Object.assign(Context.Service()(`@kutur-kit/assets/AssetBucket/${id}`), { id });
19
+ //# sourceMappingURL=AssetBucket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssetBucket.js","sources":["../src/AssetBucket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,OAAO,EAA4B,MAAM,EAAe,MAAM,QAAQ,CAAC;AA+B/F,MAAM,OAAO,gBAAiB,SAAQ,MAAM,CAAC,WAAW,EAGrD,CAAC,kBAAkB,EAAE;IACtB,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACpE,MAAM,EAAE,MAAM,CAAC,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC,MAAM;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;CACvB,CAAC;CAAG;AA8BL;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,OAAO,GAClB,GAAS,EAAE,CACX,CAA0B,EAAM,EAAiB,EAAE,CACjD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAgB,CAAC,iCAAiC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC"}