@noy-db/as-aws-s3 0.2.0-pre.20

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vLannaAi
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 ADDED
@@ -0,0 +1,43 @@
1
+ # @noy-db/as-aws-s3
2
+
3
+ An **`ObjectProjection`** for noy-db backed by AWS S3 — hold blob bytes as
4
+ **native, directly-consumable S3 objects** (presigned or public) instead of
5
+ encrypted-envelope chunks.
6
+
7
+ `to-aws-s3` is the zero-knowledge **store** (ciphertext in/out). `as-aws-s3` is
8
+ an `as-*` **projection**: raw bytes that can be served straight from S3/CDN and
9
+ processed by AWS-native tooling. It lives **outside** the zero-knowledge
10
+ guarantee — wiring it to a blob field is a deliberate, per-field opt-in.
11
+
12
+ ```ts
13
+ import { asAwsS3 } from '@noy-db/as-aws-s3'
14
+
15
+ const objects = asAwsS3({ bucket: 'acme-public-assets', region: 'eu-west-1' })
16
+
17
+ await objects.putObject('logos/acme.png', bytes, { contentType: 'image/png', public: true })
18
+ objects.publicUrl('logos/acme.png') // stable CDN/S3 URL
19
+ await objects.objectUrl('docs/x.pdf') // presigned GET (time-limited)
20
+ await objects.putUrl('videos/x.mp4', { contentType: 'video/mp4' }) // presigned PUT (direct upload)
21
+ ```
22
+
23
+ ## Options
24
+
25
+ | Option | Default | Notes |
26
+ |---|---|---|
27
+ | `bucket` | — | Target bucket (required). |
28
+ | `prefix` | `''` | Key prefix (folder). |
29
+ | `region` | `us-east-1` | AWS region. |
30
+ | `client` | new client | Share a pre-built `S3Client`. |
31
+ | `baseUrl` | virtual-hosted S3 URL | CDN origin for `publicUrl()`. |
32
+ | `defaultExpiresInSeconds` | `900` | Presigned-URL TTL. |
33
+
34
+ ## Security
35
+
36
+ Objects written here are **not encrypted** by noy-db. Prefer presigned
37
+ (`objectUrl`) over public for anything sensitive, scope the bucket tightly, and
38
+ treat public objects as **not crypto-shreddable**. See the design spec
39
+ (`docs/superpowers/specs/2026-06-15-as-aws-s3-direct-serve-blobs-design.md`).
40
+
41
+ ## Peer dependencies
42
+
43
+ `@noy-db/hub`, `@aws-sdk/client-s3`, `@aws-sdk/s3-request-presigner`.
package/dist/index.cjs ADDED
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ asAwsS3: () => asAwsS3
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var import_client_s3 = require("@aws-sdk/client-s3");
27
+ var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
28
+ function isNotFound(err) {
29
+ const e = err;
30
+ return e?.name === "NotFound" || e?.name === "NoSuchKey" || e?.$metadata?.httpStatusCode === 404;
31
+ }
32
+ function asAwsS3(options) {
33
+ const { bucket } = options;
34
+ const prefix = options.prefix ? options.prefix.replace(/\/+$/, "") + "/" : "";
35
+ const region = options.region ?? "us-east-1";
36
+ const client = options.client ?? new import_client_s3.S3Client({ region });
37
+ const presignClient = client;
38
+ const defaultExpiry = options.defaultExpiresInSeconds ?? 900;
39
+ const publicBase = (options.baseUrl ?? `https://${bucket}.s3.${region}.amazonaws.com`).replace(/\/+$/, "");
40
+ const fullKey = (key) => `${prefix}${key}`;
41
+ return {
42
+ name: "aws-s3",
43
+ async putObject(key, bytes, opts) {
44
+ await client.send(
45
+ new import_client_s3.PutObjectCommand({
46
+ Bucket: bucket,
47
+ Key: fullKey(key),
48
+ Body: bytes,
49
+ ContentType: opts.contentType,
50
+ ...opts.public ? { ACL: "public-read" } : {},
51
+ ...opts.userMeta ? { Metadata: opts.userMeta } : {}
52
+ })
53
+ );
54
+ },
55
+ async getObject(key) {
56
+ try {
57
+ const r = await client.send(new import_client_s3.GetObjectCommand({ Bucket: bucket, Key: fullKey(key) }));
58
+ if (!r.Body) return null;
59
+ return await r.Body.transformToByteArray();
60
+ } catch (err) {
61
+ if (isNotFound(err)) return null;
62
+ throw err;
63
+ }
64
+ },
65
+ async deleteObject(key) {
66
+ await client.send(new import_client_s3.DeleteObjectCommand({ Bucket: bucket, Key: fullKey(key) }));
67
+ },
68
+ async headObject(key) {
69
+ try {
70
+ const r = await client.send(new import_client_s3.HeadObjectCommand({ Bucket: bucket, Key: fullKey(key) }));
71
+ return {
72
+ size: r.ContentLength ?? 0,
73
+ ...r.ContentType ? { contentType: r.ContentType } : {},
74
+ ...r.ETag ? { etag: r.ETag } : {},
75
+ ...r.LastModified ? { lastModified: r.LastModified.toISOString() } : {},
76
+ ...r.Metadata && Object.keys(r.Metadata).length ? { userMeta: r.Metadata } : {}
77
+ };
78
+ } catch (err) {
79
+ if (isNotFound(err)) return null;
80
+ throw err;
81
+ }
82
+ },
83
+ async objectUrl(key, opts) {
84
+ return await (0, import_s3_request_presigner.getSignedUrl)(presignClient, new import_client_s3.GetObjectCommand({ Bucket: bucket, Key: fullKey(key) }), {
85
+ expiresIn: opts?.expiresInSeconds ?? defaultExpiry
86
+ });
87
+ },
88
+ async putUrl(key, opts) {
89
+ return await (0, import_s3_request_presigner.getSignedUrl)(
90
+ presignClient,
91
+ new import_client_s3.PutObjectCommand({ Bucket: bucket, Key: fullKey(key), ContentType: opts.contentType }),
92
+ { expiresIn: opts.expiresInSeconds ?? defaultExpiry }
93
+ );
94
+ },
95
+ async listPrefix(listPrefixArg) {
96
+ const out = [];
97
+ let token;
98
+ do {
99
+ const r = await client.send(
100
+ new import_client_s3.ListObjectsV2Command({ Bucket: bucket, Prefix: fullKey(listPrefixArg), ContinuationToken: token })
101
+ );
102
+ for (const obj of r.Contents ?? []) {
103
+ if (!obj.Key) continue;
104
+ const logicalKey = prefix && obj.Key.startsWith(prefix) ? obj.Key.slice(prefix.length) : obj.Key;
105
+ out.push({
106
+ key: logicalKey,
107
+ meta: {
108
+ size: obj.Size ?? 0,
109
+ ...obj.ETag ? { etag: obj.ETag } : {},
110
+ ...obj.LastModified ? { lastModified: obj.LastModified.toISOString() } : {}
111
+ }
112
+ });
113
+ }
114
+ token = r.IsTruncated ? r.NextContinuationToken : void 0;
115
+ } while (token);
116
+ return out;
117
+ },
118
+ publicUrl(key) {
119
+ return `${publicBase}/${fullKey(key)}`;
120
+ }
121
+ };
122
+ }
123
+ // Annotate the CommonJS export names for ESM import in node:
124
+ 0 && (module.exports = {
125
+ asAwsS3
126
+ });
127
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@noy-db/as-aws-s3` — an {@link ObjectProjection} backed by AWS S3.\n *\n * Where `@noy-db/to-aws-s3` is a `to-*` store (ciphertext envelopes in/out, the\n * zero-knowledge backend), this is an `as-*` **projection**: it holds blob bytes\n * as **native, directly-consumable S3 objects** so they can be served straight\n * from S3/CDN — presigned (time-limited, private) or public — and processed by\n * AWS-native tooling. It sees **raw bytes** and lives **outside** the\n * zero-knowledge guarantee; wiring it to a blob field is a deliberate opt-in.\n *\n * @example\n * ```ts\n * import { asAwsS3 } from '@noy-db/as-aws-s3'\n * const objects = asAwsS3({ bucket: 'acme-public-assets', region: 'eu-west-1' })\n * await objects.putObject('logos/acme.png', bytes, { contentType: 'image/png', public: true })\n * const url = objects.publicUrl('logos/acme.png') // stable CDN/S3 URL\n * const dl = await objects.objectUrl('logos/acme.png') // presigned GET\n * const up = await objects.putUrl('videos/x.mp4', { contentType: 'video/mp4' }) // presigned PUT\n * ```\n */\nimport type {\n ObjectProjection,\n ObjectMeta,\n PutObjectOptions,\n ObjectUrlOptions,\n PutUrlOptions,\n} from '@noy-db/hub'\nimport {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n DeleteObjectCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n} from '@aws-sdk/client-s3'\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner'\n\nexport interface AsAwsS3Options {\n /** Target bucket. */\n bucket: string\n /** Key prefix (folder). A trailing slash is added if missing. */\n prefix?: string\n /** AWS region. Default `us-east-1`. */\n region?: string\n /** Pre-built `S3Client` to share (overrides `region`). */\n client?: S3Client\n /**\n * Base URL for `publicUrl()` — a CDN origin or the bucket's website/vhost\n * URL. Defaults to the virtual-hosted S3 URL\n * (`https://{bucket}.s3.{region}.amazonaws.com`).\n */\n baseUrl?: string\n /** Default presigned-URL TTL (seconds). Default 900 (15 min). */\n defaultExpiresInSeconds?: number\n}\n\n/** The projection plus an S3-specific stable-public-URL helper. */\nexport type AsAwsS3Projection = ObjectProjection & {\n /** A stable, non-expiring URL for a **public** object (CDN/vhost). */\n publicUrl(key: string): string\n}\n\nfunction isNotFound(err: unknown): boolean {\n const e = err as { name?: string; $metadata?: { httpStatusCode?: number } }\n return e?.name === 'NotFound' || e?.name === 'NoSuchKey' || e?.$metadata?.httpStatusCode === 404\n}\n\nexport function asAwsS3(options: AsAwsS3Options): AsAwsS3Projection {\n const { bucket } = options\n const prefix = options.prefix ? options.prefix.replace(/\\/+$/, '') + '/' : ''\n const region = options.region ?? 'us-east-1'\n const client = options.client ?? new S3Client({ region })\n // getSignedUrl types its client against its own @smithy/types copy; when the\n // installed @aws-sdk/client-s3 and s3-request-presigner resolve to different\n // minor versions, S3Client and the expected Client diverge only on a private\n // `handlers` field. Runtime is identical — cast across the version skew.\n const presignClient = client as unknown as Parameters<typeof getSignedUrl>[0]\n const defaultExpiry = options.defaultExpiresInSeconds ?? 900\n const publicBase = (options.baseUrl ?? `https://${bucket}.s3.${region}.amazonaws.com`).replace(/\\/+$/, '')\n const fullKey = (key: string): string => `${prefix}${key}`\n\n return {\n name: 'aws-s3',\n\n async putObject(key: string, bytes: Uint8Array, opts: PutObjectOptions): Promise<void> {\n await client.send(\n new PutObjectCommand({\n Bucket: bucket,\n Key: fullKey(key),\n Body: bytes,\n ContentType: opts.contentType,\n ...(opts.public ? { ACL: 'public-read' as const } : {}),\n ...(opts.userMeta ? { Metadata: opts.userMeta } : {}),\n }),\n )\n },\n\n async getObject(key: string): Promise<Uint8Array | null> {\n try {\n const r = await client.send(new GetObjectCommand({ Bucket: bucket, Key: fullKey(key) }))\n if (!r.Body) return null\n return await r.Body.transformToByteArray()\n } catch (err) {\n if (isNotFound(err)) return null\n throw err\n }\n },\n\n async deleteObject(key: string): Promise<void> {\n await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: fullKey(key) }))\n },\n\n async headObject(key: string): Promise<ObjectMeta | null> {\n try {\n const r = await client.send(new HeadObjectCommand({ Bucket: bucket, Key: fullKey(key) }))\n return {\n size: r.ContentLength ?? 0,\n ...(r.ContentType ? { contentType: r.ContentType } : {}),\n ...(r.ETag ? { etag: r.ETag } : {}),\n ...(r.LastModified ? { lastModified: r.LastModified.toISOString() } : {}),\n ...(r.Metadata && Object.keys(r.Metadata).length ? { userMeta: r.Metadata } : {}),\n }\n } catch (err) {\n if (isNotFound(err)) return null\n throw err\n }\n },\n\n async objectUrl(key: string, opts?: ObjectUrlOptions): Promise<string> {\n // Always a presigned GET — works for private and public objects alike.\n // For a stable public URL, use `publicUrl()`.\n return await getSignedUrl(presignClient, new GetObjectCommand({ Bucket: bucket, Key: fullKey(key) }), {\n expiresIn: opts?.expiresInSeconds ?? defaultExpiry,\n })\n },\n\n async putUrl(key: string, opts: PutUrlOptions): Promise<string> {\n return await getSignedUrl(\n presignClient,\n new PutObjectCommand({ Bucket: bucket, Key: fullKey(key), ContentType: opts.contentType }),\n { expiresIn: opts.expiresInSeconds ?? defaultExpiry },\n )\n },\n\n async listPrefix(listPrefixArg: string) {\n const out: Array<{ key: string; meta: ObjectMeta }> = []\n let token: string | undefined\n do {\n const r = await client.send(\n new ListObjectsV2Command({ Bucket: bucket, Prefix: fullKey(listPrefixArg), ContinuationToken: token }),\n )\n for (const obj of r.Contents ?? []) {\n if (!obj.Key) continue\n // Strip the configured prefix so callers see logical keys.\n const logicalKey = prefix && obj.Key.startsWith(prefix) ? obj.Key.slice(prefix.length) : obj.Key\n out.push({\n key: logicalKey,\n meta: {\n size: obj.Size ?? 0,\n ...(obj.ETag ? { etag: obj.ETag } : {}),\n ...(obj.LastModified ? { lastModified: obj.LastModified.toISOString() } : {}),\n },\n })\n }\n token = r.IsTruncated ? r.NextContinuationToken : undefined\n } while (token)\n return out\n },\n\n publicUrl(key: string): string {\n return `${publicBase}/${fullKey(key)}`\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,50 @@
1
+ import { ObjectProjection } from '@noy-db/hub';
2
+ import { S3Client } from '@aws-sdk/client-s3';
3
+
4
+ /**
5
+ * `@noy-db/as-aws-s3` — an {@link ObjectProjection} backed by AWS S3.
6
+ *
7
+ * Where `@noy-db/to-aws-s3` is a `to-*` store (ciphertext envelopes in/out, the
8
+ * zero-knowledge backend), this is an `as-*` **projection**: it holds blob bytes
9
+ * as **native, directly-consumable S3 objects** so they can be served straight
10
+ * from S3/CDN — presigned (time-limited, private) or public — and processed by
11
+ * AWS-native tooling. It sees **raw bytes** and lives **outside** the
12
+ * zero-knowledge guarantee; wiring it to a blob field is a deliberate opt-in.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { asAwsS3 } from '@noy-db/as-aws-s3'
17
+ * const objects = asAwsS3({ bucket: 'acme-public-assets', region: 'eu-west-1' })
18
+ * await objects.putObject('logos/acme.png', bytes, { contentType: 'image/png', public: true })
19
+ * const url = objects.publicUrl('logos/acme.png') // stable CDN/S3 URL
20
+ * const dl = await objects.objectUrl('logos/acme.png') // presigned GET
21
+ * const up = await objects.putUrl('videos/x.mp4', { contentType: 'video/mp4' }) // presigned PUT
22
+ * ```
23
+ */
24
+
25
+ interface AsAwsS3Options {
26
+ /** Target bucket. */
27
+ bucket: string;
28
+ /** Key prefix (folder). A trailing slash is added if missing. */
29
+ prefix?: string;
30
+ /** AWS region. Default `us-east-1`. */
31
+ region?: string;
32
+ /** Pre-built `S3Client` to share (overrides `region`). */
33
+ client?: S3Client;
34
+ /**
35
+ * Base URL for `publicUrl()` — a CDN origin or the bucket's website/vhost
36
+ * URL. Defaults to the virtual-hosted S3 URL
37
+ * (`https://{bucket}.s3.{region}.amazonaws.com`).
38
+ */
39
+ baseUrl?: string;
40
+ /** Default presigned-URL TTL (seconds). Default 900 (15 min). */
41
+ defaultExpiresInSeconds?: number;
42
+ }
43
+ /** The projection plus an S3-specific stable-public-URL helper. */
44
+ type AsAwsS3Projection = ObjectProjection & {
45
+ /** A stable, non-expiring URL for a **public** object (CDN/vhost). */
46
+ publicUrl(key: string): string;
47
+ };
48
+ declare function asAwsS3(options: AsAwsS3Options): AsAwsS3Projection;
49
+
50
+ export { type AsAwsS3Options, type AsAwsS3Projection, asAwsS3 };
@@ -0,0 +1,50 @@
1
+ import { ObjectProjection } from '@noy-db/hub';
2
+ import { S3Client } from '@aws-sdk/client-s3';
3
+
4
+ /**
5
+ * `@noy-db/as-aws-s3` — an {@link ObjectProjection} backed by AWS S3.
6
+ *
7
+ * Where `@noy-db/to-aws-s3` is a `to-*` store (ciphertext envelopes in/out, the
8
+ * zero-knowledge backend), this is an `as-*` **projection**: it holds blob bytes
9
+ * as **native, directly-consumable S3 objects** so they can be served straight
10
+ * from S3/CDN — presigned (time-limited, private) or public — and processed by
11
+ * AWS-native tooling. It sees **raw bytes** and lives **outside** the
12
+ * zero-knowledge guarantee; wiring it to a blob field is a deliberate opt-in.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { asAwsS3 } from '@noy-db/as-aws-s3'
17
+ * const objects = asAwsS3({ bucket: 'acme-public-assets', region: 'eu-west-1' })
18
+ * await objects.putObject('logos/acme.png', bytes, { contentType: 'image/png', public: true })
19
+ * const url = objects.publicUrl('logos/acme.png') // stable CDN/S3 URL
20
+ * const dl = await objects.objectUrl('logos/acme.png') // presigned GET
21
+ * const up = await objects.putUrl('videos/x.mp4', { contentType: 'video/mp4' }) // presigned PUT
22
+ * ```
23
+ */
24
+
25
+ interface AsAwsS3Options {
26
+ /** Target bucket. */
27
+ bucket: string;
28
+ /** Key prefix (folder). A trailing slash is added if missing. */
29
+ prefix?: string;
30
+ /** AWS region. Default `us-east-1`. */
31
+ region?: string;
32
+ /** Pre-built `S3Client` to share (overrides `region`). */
33
+ client?: S3Client;
34
+ /**
35
+ * Base URL for `publicUrl()` — a CDN origin or the bucket's website/vhost
36
+ * URL. Defaults to the virtual-hosted S3 URL
37
+ * (`https://{bucket}.s3.{region}.amazonaws.com`).
38
+ */
39
+ baseUrl?: string;
40
+ /** Default presigned-URL TTL (seconds). Default 900 (15 min). */
41
+ defaultExpiresInSeconds?: number;
42
+ }
43
+ /** The projection plus an S3-specific stable-public-URL helper. */
44
+ type AsAwsS3Projection = ObjectProjection & {
45
+ /** A stable, non-expiring URL for a **public** object (CDN/vhost). */
46
+ publicUrl(key: string): string;
47
+ };
48
+ declare function asAwsS3(options: AsAwsS3Options): AsAwsS3Projection;
49
+
50
+ export { type AsAwsS3Options, type AsAwsS3Projection, asAwsS3 };
package/dist/index.js ADDED
@@ -0,0 +1,109 @@
1
+ // src/index.ts
2
+ import {
3
+ S3Client,
4
+ PutObjectCommand,
5
+ GetObjectCommand,
6
+ DeleteObjectCommand,
7
+ HeadObjectCommand,
8
+ ListObjectsV2Command
9
+ } from "@aws-sdk/client-s3";
10
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
11
+ function isNotFound(err) {
12
+ const e = err;
13
+ return e?.name === "NotFound" || e?.name === "NoSuchKey" || e?.$metadata?.httpStatusCode === 404;
14
+ }
15
+ function asAwsS3(options) {
16
+ const { bucket } = options;
17
+ const prefix = options.prefix ? options.prefix.replace(/\/+$/, "") + "/" : "";
18
+ const region = options.region ?? "us-east-1";
19
+ const client = options.client ?? new S3Client({ region });
20
+ const presignClient = client;
21
+ const defaultExpiry = options.defaultExpiresInSeconds ?? 900;
22
+ const publicBase = (options.baseUrl ?? `https://${bucket}.s3.${region}.amazonaws.com`).replace(/\/+$/, "");
23
+ const fullKey = (key) => `${prefix}${key}`;
24
+ return {
25
+ name: "aws-s3",
26
+ async putObject(key, bytes, opts) {
27
+ await client.send(
28
+ new PutObjectCommand({
29
+ Bucket: bucket,
30
+ Key: fullKey(key),
31
+ Body: bytes,
32
+ ContentType: opts.contentType,
33
+ ...opts.public ? { ACL: "public-read" } : {},
34
+ ...opts.userMeta ? { Metadata: opts.userMeta } : {}
35
+ })
36
+ );
37
+ },
38
+ async getObject(key) {
39
+ try {
40
+ const r = await client.send(new GetObjectCommand({ Bucket: bucket, Key: fullKey(key) }));
41
+ if (!r.Body) return null;
42
+ return await r.Body.transformToByteArray();
43
+ } catch (err) {
44
+ if (isNotFound(err)) return null;
45
+ throw err;
46
+ }
47
+ },
48
+ async deleteObject(key) {
49
+ await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: fullKey(key) }));
50
+ },
51
+ async headObject(key) {
52
+ try {
53
+ const r = await client.send(new HeadObjectCommand({ Bucket: bucket, Key: fullKey(key) }));
54
+ return {
55
+ size: r.ContentLength ?? 0,
56
+ ...r.ContentType ? { contentType: r.ContentType } : {},
57
+ ...r.ETag ? { etag: r.ETag } : {},
58
+ ...r.LastModified ? { lastModified: r.LastModified.toISOString() } : {},
59
+ ...r.Metadata && Object.keys(r.Metadata).length ? { userMeta: r.Metadata } : {}
60
+ };
61
+ } catch (err) {
62
+ if (isNotFound(err)) return null;
63
+ throw err;
64
+ }
65
+ },
66
+ async objectUrl(key, opts) {
67
+ return await getSignedUrl(presignClient, new GetObjectCommand({ Bucket: bucket, Key: fullKey(key) }), {
68
+ expiresIn: opts?.expiresInSeconds ?? defaultExpiry
69
+ });
70
+ },
71
+ async putUrl(key, opts) {
72
+ return await getSignedUrl(
73
+ presignClient,
74
+ new PutObjectCommand({ Bucket: bucket, Key: fullKey(key), ContentType: opts.contentType }),
75
+ { expiresIn: opts.expiresInSeconds ?? defaultExpiry }
76
+ );
77
+ },
78
+ async listPrefix(listPrefixArg) {
79
+ const out = [];
80
+ let token;
81
+ do {
82
+ const r = await client.send(
83
+ new ListObjectsV2Command({ Bucket: bucket, Prefix: fullKey(listPrefixArg), ContinuationToken: token })
84
+ );
85
+ for (const obj of r.Contents ?? []) {
86
+ if (!obj.Key) continue;
87
+ const logicalKey = prefix && obj.Key.startsWith(prefix) ? obj.Key.slice(prefix.length) : obj.Key;
88
+ out.push({
89
+ key: logicalKey,
90
+ meta: {
91
+ size: obj.Size ?? 0,
92
+ ...obj.ETag ? { etag: obj.ETag } : {},
93
+ ...obj.LastModified ? { lastModified: obj.LastModified.toISOString() } : {}
94
+ }
95
+ });
96
+ }
97
+ token = r.IsTruncated ? r.NextContinuationToken : void 0;
98
+ } while (token);
99
+ return out;
100
+ },
101
+ publicUrl(key) {
102
+ return `${publicBase}/${fullKey(key)}`;
103
+ }
104
+ };
105
+ }
106
+ export {
107
+ asAwsS3
108
+ };
109
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@noy-db/as-aws-s3` — an {@link ObjectProjection} backed by AWS S3.\n *\n * Where `@noy-db/to-aws-s3` is a `to-*` store (ciphertext envelopes in/out, the\n * zero-knowledge backend), this is an `as-*` **projection**: it holds blob bytes\n * as **native, directly-consumable S3 objects** so they can be served straight\n * from S3/CDN — presigned (time-limited, private) or public — and processed by\n * AWS-native tooling. It sees **raw bytes** and lives **outside** the\n * zero-knowledge guarantee; wiring it to a blob field is a deliberate opt-in.\n *\n * @example\n * ```ts\n * import { asAwsS3 } from '@noy-db/as-aws-s3'\n * const objects = asAwsS3({ bucket: 'acme-public-assets', region: 'eu-west-1' })\n * await objects.putObject('logos/acme.png', bytes, { contentType: 'image/png', public: true })\n * const url = objects.publicUrl('logos/acme.png') // stable CDN/S3 URL\n * const dl = await objects.objectUrl('logos/acme.png') // presigned GET\n * const up = await objects.putUrl('videos/x.mp4', { contentType: 'video/mp4' }) // presigned PUT\n * ```\n */\nimport type {\n ObjectProjection,\n ObjectMeta,\n PutObjectOptions,\n ObjectUrlOptions,\n PutUrlOptions,\n} from '@noy-db/hub'\nimport {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n DeleteObjectCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n} from '@aws-sdk/client-s3'\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner'\n\nexport interface AsAwsS3Options {\n /** Target bucket. */\n bucket: string\n /** Key prefix (folder). A trailing slash is added if missing. */\n prefix?: string\n /** AWS region. Default `us-east-1`. */\n region?: string\n /** Pre-built `S3Client` to share (overrides `region`). */\n client?: S3Client\n /**\n * Base URL for `publicUrl()` — a CDN origin or the bucket's website/vhost\n * URL. Defaults to the virtual-hosted S3 URL\n * (`https://{bucket}.s3.{region}.amazonaws.com`).\n */\n baseUrl?: string\n /** Default presigned-URL TTL (seconds). Default 900 (15 min). */\n defaultExpiresInSeconds?: number\n}\n\n/** The projection plus an S3-specific stable-public-URL helper. */\nexport type AsAwsS3Projection = ObjectProjection & {\n /** A stable, non-expiring URL for a **public** object (CDN/vhost). */\n publicUrl(key: string): string\n}\n\nfunction isNotFound(err: unknown): boolean {\n const e = err as { name?: string; $metadata?: { httpStatusCode?: number } }\n return e?.name === 'NotFound' || e?.name === 'NoSuchKey' || e?.$metadata?.httpStatusCode === 404\n}\n\nexport function asAwsS3(options: AsAwsS3Options): AsAwsS3Projection {\n const { bucket } = options\n const prefix = options.prefix ? options.prefix.replace(/\\/+$/, '') + '/' : ''\n const region = options.region ?? 'us-east-1'\n const client = options.client ?? new S3Client({ region })\n // getSignedUrl types its client against its own @smithy/types copy; when the\n // installed @aws-sdk/client-s3 and s3-request-presigner resolve to different\n // minor versions, S3Client and the expected Client diverge only on a private\n // `handlers` field. Runtime is identical — cast across the version skew.\n const presignClient = client as unknown as Parameters<typeof getSignedUrl>[0]\n const defaultExpiry = options.defaultExpiresInSeconds ?? 900\n const publicBase = (options.baseUrl ?? `https://${bucket}.s3.${region}.amazonaws.com`).replace(/\\/+$/, '')\n const fullKey = (key: string): string => `${prefix}${key}`\n\n return {\n name: 'aws-s3',\n\n async putObject(key: string, bytes: Uint8Array, opts: PutObjectOptions): Promise<void> {\n await client.send(\n new PutObjectCommand({\n Bucket: bucket,\n Key: fullKey(key),\n Body: bytes,\n ContentType: opts.contentType,\n ...(opts.public ? { ACL: 'public-read' as const } : {}),\n ...(opts.userMeta ? { Metadata: opts.userMeta } : {}),\n }),\n )\n },\n\n async getObject(key: string): Promise<Uint8Array | null> {\n try {\n const r = await client.send(new GetObjectCommand({ Bucket: bucket, Key: fullKey(key) }))\n if (!r.Body) return null\n return await r.Body.transformToByteArray()\n } catch (err) {\n if (isNotFound(err)) return null\n throw err\n }\n },\n\n async deleteObject(key: string): Promise<void> {\n await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: fullKey(key) }))\n },\n\n async headObject(key: string): Promise<ObjectMeta | null> {\n try {\n const r = await client.send(new HeadObjectCommand({ Bucket: bucket, Key: fullKey(key) }))\n return {\n size: r.ContentLength ?? 0,\n ...(r.ContentType ? { contentType: r.ContentType } : {}),\n ...(r.ETag ? { etag: r.ETag } : {}),\n ...(r.LastModified ? { lastModified: r.LastModified.toISOString() } : {}),\n ...(r.Metadata && Object.keys(r.Metadata).length ? { userMeta: r.Metadata } : {}),\n }\n } catch (err) {\n if (isNotFound(err)) return null\n throw err\n }\n },\n\n async objectUrl(key: string, opts?: ObjectUrlOptions): Promise<string> {\n // Always a presigned GET — works for private and public objects alike.\n // For a stable public URL, use `publicUrl()`.\n return await getSignedUrl(presignClient, new GetObjectCommand({ Bucket: bucket, Key: fullKey(key) }), {\n expiresIn: opts?.expiresInSeconds ?? defaultExpiry,\n })\n },\n\n async putUrl(key: string, opts: PutUrlOptions): Promise<string> {\n return await getSignedUrl(\n presignClient,\n new PutObjectCommand({ Bucket: bucket, Key: fullKey(key), ContentType: opts.contentType }),\n { expiresIn: opts.expiresInSeconds ?? defaultExpiry },\n )\n },\n\n async listPrefix(listPrefixArg: string) {\n const out: Array<{ key: string; meta: ObjectMeta }> = []\n let token: string | undefined\n do {\n const r = await client.send(\n new ListObjectsV2Command({ Bucket: bucket, Prefix: fullKey(listPrefixArg), ContinuationToken: token }),\n )\n for (const obj of r.Contents ?? []) {\n if (!obj.Key) continue\n // Strip the configured prefix so callers see logical keys.\n const logicalKey = prefix && obj.Key.startsWith(prefix) ? obj.Key.slice(prefix.length) : obj.Key\n out.push({\n key: logicalKey,\n meta: {\n size: obj.Size ?? 0,\n ...(obj.ETag ? { etag: obj.ETag } : {}),\n ...(obj.LastModified ? { lastModified: obj.LastModified.toISOString() } : {}),\n },\n })\n }\n token = r.IsTruncated ? r.NextContinuationToken : undefined\n } while (token)\n return out\n },\n\n publicUrl(key: string): string {\n return `${publicBase}/${fullKey(key)}`\n },\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@noy-db/as-aws-s3",
3
+ "version": "0.2.0-pre.20",
4
+ "description": "AWS S3 object projection for noy-db — serve blob bytes directly from S3/CDN (presigned or public), outside the encrypted store",
5
+ "license": "MIT",
6
+ "author": "vLannaAi <vicio@lanna.ai>",
7
+ "homepage": "https://github.com/vLannaAi/noy-db/tree/main/packages/as-aws-s3#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/vLannaAi/noy-db.git",
11
+ "directory": "packages/as-aws-s3"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/vLannaAi/noy-db/issues"
15
+ },
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": {
20
+ "import": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "require": {
25
+ "types": "./dist/index.d.cts",
26
+ "default": "./dist/index.cjs"
27
+ }
28
+ }
29
+ },
30
+ "main": "./dist/index.cjs",
31
+ "module": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "files": [
34
+ "dist",
35
+ "README.md",
36
+ "LICENSE"
37
+ ],
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "peerDependencies": {
42
+ "@aws-sdk/client-s3": "^3.0.0",
43
+ "@aws-sdk/s3-request-presigner": "^3.0.0",
44
+ "@noy-db/hub": "0.2.0-pre.20"
45
+ },
46
+ "devDependencies": {
47
+ "@aws-sdk/client-s3": "^3.0.0",
48
+ "@aws-sdk/s3-request-presigner": "^3.0.0",
49
+ "@noy-db/hub": "0.2.0-pre.20"
50
+ },
51
+ "keywords": [
52
+ "noy-db",
53
+ "projection",
54
+ "s3",
55
+ "aws",
56
+ "object-storage",
57
+ "presigned-url",
58
+ "cdn",
59
+ "direct-serve"
60
+ ],
61
+ "publishConfig": {
62
+ "access": "public",
63
+ "tag": "latest"
64
+ },
65
+ "scripts": {
66
+ "build": "tsup",
67
+ "test": "vitest run",
68
+ "lint": "eslint src/",
69
+ "typecheck": "tsc --noEmit"
70
+ }
71
+ }