bunderstack 0.25.1 → 1.0.0-beta.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/CHANGELOG.md +43 -0
- package/dist/auth-password.d.ts +27 -0
- package/dist/auth-password.d.ts.map +1 -0
- package/dist/auth-password.js +60 -0
- package/dist/auth-password.js.map +1 -0
- package/dist/auth.d.ts +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +1 -0
- package/dist/auth.js.map +1 -1
- package/dist/backend.d.ts +3 -0
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +3 -1
- package/dist/backend.js.map +1 -1
- package/dist/cli.d.ts +11 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +97 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +0 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -18
- package/dist/config.js.map +1 -1
- package/dist/crud-operations.d.ts.map +1 -1
- package/dist/crud-operations.js +19 -3
- package/dist/crud-operations.js.map +1 -1
- package/dist/dev/binaries.d.ts +22 -0
- package/dist/dev/binaries.d.ts.map +1 -0
- package/dist/dev/binaries.js +119 -0
- package/dist/dev/binaries.js.map +1 -0
- package/dist/dev/env.d.ts +11 -0
- package/dist/dev/env.d.ts.map +1 -0
- package/dist/dev/env.js +71 -0
- package/dist/dev/env.js.map +1 -0
- package/dist/dev/index.d.ts +45 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +287 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/processes.d.ts +37 -0
- package/dist/dev/processes.d.ts.map +1 -0
- package/dist/dev/processes.js +104 -0
- package/dist/dev/processes.js.map +1 -0
- package/dist/dev/push.d.ts +2 -0
- package/dist/dev/push.d.ts.map +1 -0
- package/dist/dev/push.js +25 -0
- package/dist/dev/push.js.map +1 -0
- package/dist/env-probe.d.ts.map +1 -1
- package/dist/env-probe.js +0 -1
- package/dist/env-probe.js.map +1 -1
- package/dist/env.d.ts +0 -3
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +0 -7
- package/dist/env.js.map +1 -1
- package/dist/handler.d.ts +2 -0
- package/dist/handler.d.ts.map +1 -1
- package/dist/handler.js +1 -2
- package/dist/handler.js.map +1 -1
- package/dist/hosted-contract.d.ts.map +1 -1
- package/dist/hosted-contract.js +7 -3
- package/dist/hosted-contract.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jobs/define.d.ts +5 -0
- package/dist/jobs/define.d.ts.map +1 -1
- package/dist/jobs/define.js.map +1 -1
- package/dist/jobs/index.d.ts +2 -4
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +2 -3
- package/dist/jobs/index.js.map +1 -1
- package/dist/jobs/queue.d.ts +1 -0
- package/dist/jobs/queue.d.ts.map +1 -1
- package/dist/jobs/queue.js +6 -3
- package/dist/jobs/queue.js.map +1 -1
- package/dist/jobs/slots.d.ts +5 -0
- package/dist/jobs/slots.d.ts.map +1 -1
- package/dist/jobs/slots.js +12 -0
- package/dist/jobs/slots.js.map +1 -1
- package/dist/jobs/worker.d.ts +6 -0
- package/dist/jobs/worker.d.ts.map +1 -1
- package/dist/jobs/worker.js +32 -1
- package/dist/jobs/worker.js.map +1 -1
- package/dist/platform.d.ts +25 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +24 -0
- package/dist/platform.js.map +1 -0
- package/dist/query/client.js +1 -1
- package/dist/query/client.js.map +1 -1
- package/dist/rate-limit.d.ts +2 -1
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +19 -22
- package/dist/rate-limit.js.map +1 -1
- package/dist/realtime/facade.d.ts +1 -1
- package/dist/realtime/facade.d.ts.map +1 -1
- package/dist/realtime/facade.js.map +1 -1
- package/dist/realtime/publisher.d.ts +0 -4
- package/dist/realtime/publisher.d.ts.map +1 -1
- package/dist/realtime/publisher.js +0 -9
- package/dist/realtime/publisher.js.map +1 -1
- package/dist/runtime.d.ts +5 -19
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +28 -102
- package/dist/runtime.js.map +1 -1
- package/dist/storage/registry.d.ts +2 -1
- package/dist/storage/registry.d.ts.map +1 -1
- package/dist/storage/registry.js +2 -2
- package/dist/storage/registry.js.map +1 -1
- package/dist/storage/s3.d.ts +11 -4
- package/dist/storage/s3.d.ts.map +1 -1
- package/dist/storage/s3.js +88 -34
- package/dist/storage/s3.js.map +1 -1
- package/dist/storage/sigv4.d.ts +24 -0
- package/dist/storage/sigv4.d.ts.map +1 -0
- package/dist/storage/sigv4.js +107 -0
- package/dist/storage/sigv4.js.map +1 -0
- package/dist/testing/fixture.d.ts.map +1 -1
- package/dist/testing/fixture.js +0 -3
- package/dist/testing/fixture.js.map +1 -1
- package/dist/testing/workers-fakes.d.ts +17 -0
- package/dist/testing/workers-fakes.d.ts.map +1 -0
- package/dist/testing/workers-fakes.js +98 -0
- package/dist/testing/workers-fakes.js.map +1 -0
- package/dist/typeid.d.ts +1 -0
- package/dist/typeid.d.ts.map +1 -1
- package/dist/typeid.js +34 -4
- package/dist/typeid.js.map +1 -1
- package/dist/vite.d.ts +30 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +37 -0
- package/dist/vite.js.map +1 -0
- package/dist/workers/app.d.ts +19 -0
- package/dist/workers/app.d.ts.map +1 -0
- package/dist/workers/app.js +56 -0
- package/dist/workers/app.js.map +1 -0
- package/dist/workers/index.d.ts +21 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +33 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/r2.d.ts +23 -0
- package/dist/workers/r2.d.ts.map +1 -0
- package/dist/workers/r2.js +77 -0
- package/dist/workers/r2.js.map +1 -0
- package/dist/workers/rate-limiter.d.ts +9 -0
- package/dist/workers/rate-limiter.d.ts.map +1 -0
- package/dist/workers/rate-limiter.js +22 -0
- package/dist/workers/rate-limiter.js.map +1 -0
- package/dist/workers/realtime-hub.d.ts +20 -0
- package/dist/workers/realtime-hub.d.ts.map +1 -0
- package/dist/workers/realtime-hub.js +117 -0
- package/dist/workers/realtime-hub.js.map +1 -0
- package/dist/workers/scheduler.d.ts +13 -0
- package/dist/workers/scheduler.d.ts.map +1 -0
- package/dist/workers/scheduler.js +71 -0
- package/dist/workers/scheduler.js.map +1 -0
- package/dist/workers/types.d.ts +55 -0
- package/dist/workers/types.d.ts.map +1 -0
- package/dist/workers/types.js +4 -0
- package/dist/workers/types.js.map +1 -0
- package/dist/workers/wrangler.d.ts +64 -0
- package/dist/workers/wrangler.d.ts.map +1 -0
- package/dist/workers/wrangler.js +115 -0
- package/dist/workers/wrangler.js.map +1 -0
- package/llms-full.txt +10 -1
- package/package.json +11 -3
- package/dist/jobs/runtime.d.ts +0 -17
- package/dist/jobs/runtime.d.ts.map +0 -1
- package/dist/jobs/runtime.js +0 -56
- package/dist/jobs/runtime.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/storage/registry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/storage/registry.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,MAAM,CAAA;AAQvC,MAAM,UAAU,aAAa,CAAC,OAAwB;IACpD,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC7B,OAAO,IAAI,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9C,CAAC;IACD,OAAO,IAAI,gBAAgB,CAAC;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;KAC7B,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,QAAgC,EAChC,UAAiC,aAAa;IAE9C,MAAM,QAAQ,GAA0B,IAAI,GAAG,EAAE,CAAA;IACjD,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;QAC9C,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;IAC1E,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC","sourcesContent":["// src/storage/registry.ts\nimport type { StorageAdapterFactory } from '../platform'\nimport type {\n ResolvedBackend,\n ResolvedBucket,\n ResolvedStorageBuckets,\n} from './buckets'\nimport type { StorageAdapter } from './index'\n\nimport { LocalStorageAdapter } from './local'\nimport { S3StorageAdapter } from './s3'\n\nexport interface BucketStorage {\n bucket: ResolvedBucket\n adapter: StorageAdapter\n}\nexport type BucketStorageRegistry = Map<string, BucketStorage>\n\nexport function createAdapter(backend: ResolvedBackend): StorageAdapter {\n if (backend.type === 'local') {\n return new LocalStorageAdapter(backend.path)\n }\n return new S3StorageAdapter({\n bucket: backend.bucket,\n region: backend.region,\n accessKeyId: backend.accessKeyId,\n secretAccessKey: backend.secretAccessKey,\n endpoint: backend.endpoint,\n publicUrl: backend.publicUrl,\n })\n}\n\nexport function createBucketStorages(\n resolved: ResolvedStorageBuckets,\n factory: StorageAdapterFactory = createAdapter,\n): BucketStorageRegistry {\n const registry: BucketStorageRegistry = new Map()\n for (const [name, bucket] of resolved.buckets) {\n registry.set(name, { bucket, adapter: factory(bucket.backend, bucket) })\n }\n return registry\n}\n"]}
|
package/dist/storage/s3.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PresignGetOptions, PresignPutOptions, StorageAdapter } from './index.js';
|
|
2
|
+
type Fetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
2
3
|
interface S3Config {
|
|
3
4
|
bucket: string;
|
|
4
5
|
region: string;
|
|
@@ -6,23 +7,29 @@ interface S3Config {
|
|
|
6
7
|
secretAccessKey: string;
|
|
7
8
|
endpoint?: string;
|
|
8
9
|
publicUrl?: string;
|
|
10
|
+
/** Injected in tests; defaults to the global fetch. */
|
|
11
|
+
fetch?: Fetch;
|
|
9
12
|
}
|
|
10
13
|
export declare class S3StorageAdapter implements StorageAdapter {
|
|
11
|
-
private
|
|
14
|
+
private readonly base;
|
|
15
|
+
private readonly credentials;
|
|
12
16
|
private readonly publicUrl?;
|
|
17
|
+
private readonly fetchFn;
|
|
13
18
|
constructor(cfg: S3Config);
|
|
19
|
+
private objectUrl;
|
|
20
|
+
private send;
|
|
14
21
|
upload(fileId: string, data: Blob | ArrayBuffer, contentType: string): Promise<void>;
|
|
15
22
|
get(fileId: string): Promise<Response>;
|
|
16
23
|
delete(fileId: string): Promise<void>;
|
|
17
24
|
exists(fileId: string): Promise<boolean>;
|
|
18
|
-
presignPut(key: string, opts: PresignPutOptions): Promise<string>;
|
|
19
|
-
presignGet(key: string, opts: PresignGetOptions): Promise<string>;
|
|
20
25
|
stat(key: string): Promise<{
|
|
21
26
|
size: number;
|
|
22
27
|
contentType: string;
|
|
23
28
|
} | null>;
|
|
24
|
-
publicUrlFor(key: string): string | undefined;
|
|
25
29
|
list(prefix: string): Promise<string[]>;
|
|
30
|
+
presignPut(key: string, opts: PresignPutOptions): Promise<string>;
|
|
31
|
+
presignGet(key: string, opts: PresignGetOptions): Promise<string>;
|
|
32
|
+
publicUrlFor(key: string): string | undefined;
|
|
26
33
|
}
|
|
27
34
|
export {};
|
|
28
35
|
//# sourceMappingURL=s3.d.ts.map
|
package/dist/storage/s3.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../src/storage/s3.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACf,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../src/storage/s3.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACf,MAAM,SAAS,CAAA;AAIhB,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAEhF,UAAU,QAAQ;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uDAAuD;IACvD,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAWD,qBAAa,gBAAiB,YAAW,cAAc;IACrD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAQ;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAO;gBAEnB,GAAG,EAAE,QAAQ;IAczB,OAAO,CAAC,SAAS;YAIH,IAAI;IAgBZ,MAAM,CACV,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,IAAI,GAAG,WAAW,EACxB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAYV,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IActC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOrC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIxC,IAAI,CACR,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IASlD,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IA4BvC,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;IASjE,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;IASvE,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAI9C"}
|
package/dist/storage/s3.js
CHANGED
|
@@ -1,71 +1,125 @@
|
|
|
1
|
+
import { presignUrl, signRequest } from './sigv4.js';
|
|
2
|
+
function decodeXml(value) {
|
|
3
|
+
return value
|
|
4
|
+
.replace(/</g, '<')
|
|
5
|
+
.replace(/>/g, '>')
|
|
6
|
+
.replace(/"/g, '"')
|
|
7
|
+
.replace(/'/g, "'")
|
|
8
|
+
.replace(/&/g, '&');
|
|
9
|
+
}
|
|
1
10
|
export class S3StorageAdapter {
|
|
2
|
-
|
|
11
|
+
base;
|
|
12
|
+
credentials;
|
|
3
13
|
publicUrl;
|
|
14
|
+
fetchFn;
|
|
4
15
|
constructor(cfg) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
16
|
+
const endpoint = (cfg.endpoint ?? `https://s3.${cfg.region}.amazonaws.com`).replace(/\/$/, '');
|
|
17
|
+
this.base = `${endpoint}/${cfg.bucket}`;
|
|
18
|
+
this.credentials = {
|
|
8
19
|
accessKeyId: cfg.accessKeyId,
|
|
9
20
|
secretAccessKey: cfg.secretAccessKey,
|
|
10
|
-
|
|
11
|
-
}
|
|
21
|
+
region: cfg.region,
|
|
22
|
+
};
|
|
12
23
|
this.publicUrl = cfg.publicUrl;
|
|
24
|
+
this.fetchFn = cfg.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
25
|
+
}
|
|
26
|
+
objectUrl(key) {
|
|
27
|
+
return `${this.base}/${key.split('/').map(encodeURIComponent).join('/')}`;
|
|
28
|
+
}
|
|
29
|
+
async send(method, url, body, headers = {}) {
|
|
30
|
+
const signed = await signRequest({
|
|
31
|
+
method,
|
|
32
|
+
url,
|
|
33
|
+
headers,
|
|
34
|
+
body: body ?? null,
|
|
35
|
+
credentials: this.credentials,
|
|
36
|
+
});
|
|
37
|
+
return this.fetchFn(url, { method, headers: signed, body });
|
|
13
38
|
}
|
|
14
39
|
async upload(fileId, data, contentType) {
|
|
15
40
|
const bytes = data instanceof Blob ? await data.arrayBuffer() : data;
|
|
16
|
-
await this.
|
|
41
|
+
const res = await this.send('PUT', this.objectUrl(fileId), bytes, {
|
|
42
|
+
'content-type': contentType,
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
throw new Error(`[bunderstack] S3 upload failed (${res.status}): ${await res.text()}`);
|
|
46
|
+
}
|
|
17
47
|
}
|
|
18
48
|
async get(fileId) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// `type: ""` — it never stats, and unlike `Bun.file` it does not guess
|
|
22
|
-
// from the extension either.
|
|
23
|
-
const info = await this.stat(fileId);
|
|
24
|
-
if (!info)
|
|
49
|
+
const res = await this.send('GET', this.objectUrl(fileId));
|
|
50
|
+
if (res.status === 404)
|
|
25
51
|
return new Response('Not found', { status: 404 });
|
|
26
|
-
|
|
27
|
-
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
throw new Error(`[bunderstack] S3 get failed (${res.status})`);
|
|
54
|
+
}
|
|
55
|
+
return new Response(res.body, {
|
|
28
56
|
headers: {
|
|
29
|
-
'Content-Type':
|
|
57
|
+
'Content-Type': res.headers.get('content-type') || 'application/octet-stream',
|
|
30
58
|
},
|
|
31
59
|
});
|
|
32
60
|
}
|
|
33
61
|
async delete(fileId) {
|
|
34
|
-
await this.
|
|
62
|
+
const res = await this.send('DELETE', this.objectUrl(fileId));
|
|
63
|
+
if (!res.ok && res.status !== 404) {
|
|
64
|
+
throw new Error(`[bunderstack] S3 delete failed (${res.status})`);
|
|
65
|
+
}
|
|
35
66
|
}
|
|
36
67
|
async exists(fileId) {
|
|
37
|
-
return this.
|
|
68
|
+
return (await this.stat(fileId)) !== null;
|
|
38
69
|
}
|
|
70
|
+
async stat(key) {
|
|
71
|
+
const res = await this.send('HEAD', this.objectUrl(key));
|
|
72
|
+
if (!res.ok)
|
|
73
|
+
return null;
|
|
74
|
+
return {
|
|
75
|
+
size: Number(res.headers.get('content-length') ?? 0),
|
|
76
|
+
contentType: res.headers.get('content-type') ?? '',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async list(prefix) {
|
|
80
|
+
const keys = [];
|
|
81
|
+
let token;
|
|
82
|
+
do {
|
|
83
|
+
const url = new URL(this.base);
|
|
84
|
+
url.searchParams.set('list-type', '2');
|
|
85
|
+
url.searchParams.set('prefix', prefix);
|
|
86
|
+
if (token)
|
|
87
|
+
url.searchParams.set('continuation-token', token);
|
|
88
|
+
const res = await this.send('GET', url.toString());
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
throw new Error(`[bunderstack] S3 list failed (${res.status})`);
|
|
91
|
+
}
|
|
92
|
+
const xml = await res.text();
|
|
93
|
+
for (const match of xml.matchAll(/<Key>([\s\S]*?)<\/Key>/g)) {
|
|
94
|
+
keys.push(decodeXml(match[1]));
|
|
95
|
+
}
|
|
96
|
+
token = /<IsTruncated>true<\/IsTruncated>/.test(xml)
|
|
97
|
+
? decodeXml(/<NextContinuationToken>([\s\S]*?)<\/NextContinuationToken>/.exec(xml)?.[1] ?? '')
|
|
98
|
+
: undefined;
|
|
99
|
+
} while (token);
|
|
100
|
+
return keys;
|
|
101
|
+
}
|
|
102
|
+
// The content type is not signed: confirmUpload checks the stored type.
|
|
39
103
|
async presignPut(key, opts) {
|
|
40
|
-
return
|
|
104
|
+
return presignUrl({
|
|
41
105
|
method: 'PUT',
|
|
106
|
+
url: this.objectUrl(key),
|
|
107
|
+
credentials: this.credentials,
|
|
42
108
|
expiresIn: opts.expiresIn,
|
|
43
|
-
...(opts.contentType && { type: opts.contentType }),
|
|
44
109
|
});
|
|
45
110
|
}
|
|
46
111
|
async presignGet(key, opts) {
|
|
47
|
-
return
|
|
112
|
+
return presignUrl({
|
|
48
113
|
method: 'GET',
|
|
114
|
+
url: this.objectUrl(key),
|
|
115
|
+
credentials: this.credentials,
|
|
49
116
|
expiresIn: opts.expiresIn,
|
|
50
117
|
});
|
|
51
118
|
}
|
|
52
|
-
async stat(key) {
|
|
53
|
-
try {
|
|
54
|
-
const s = await this.client.stat(key);
|
|
55
|
-
return { size: s.size, contentType: s.type };
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
119
|
publicUrlFor(key) {
|
|
62
120
|
if (!this.publicUrl)
|
|
63
121
|
return undefined;
|
|
64
122
|
return `${this.publicUrl.replace(/\/$/, '')}/${key}`;
|
|
65
123
|
}
|
|
66
|
-
async list(prefix) {
|
|
67
|
-
const res = await this.client.list({ prefix });
|
|
68
|
-
return res.contents?.map((c) => c.key) ?? [];
|
|
69
|
-
}
|
|
70
124
|
}
|
|
71
125
|
//# sourceMappingURL=s3.js.map
|
package/dist/storage/s3.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"s3.js","sourceRoot":"","sources":["../../src/storage/s3.ts"],"names":[],"mappings":"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","sourcesContent":["// src/storage/s3.ts\nimport type {\n PresignGetOptions,\n PresignPutOptions,\n StorageAdapter,\n} from './index'\n\ninterface S3Config {\n bucket: string\n region: string\n accessKeyId: string\n secretAccessKey: string\n endpoint?: string\n publicUrl?: string\n}\n\nexport class S3StorageAdapter implements StorageAdapter {\n private client: InstanceType<typeof Bun.S3Client>\n private readonly publicUrl?: string\n\n constructor(cfg: S3Config) {\n this.client = new Bun.S3Client({\n bucket: cfg.bucket,\n region: cfg.region,\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n ...(cfg.endpoint && { endpoint: cfg.endpoint }),\n })\n this.publicUrl = cfg.publicUrl\n }\n\n async upload(\n fileId: string,\n data: Blob | ArrayBuffer,\n contentType: string,\n ): Promise<void> {\n const bytes = data instanceof Blob ? await data.arrayBuffer() : data\n await this.client.write(fileId, bytes, { type: contentType })\n }\n\n async get(fileId: string): Promise<Response> {\n // `stat` (a HEAD, same cost as `exists`) is the only way to learn the\n // stored Content-Type: a lazily-constructed `client.file(key)` reports\n // `type: \"\"` — it never stats, and unlike `Bun.file` it does not guess\n // from the extension either.\n const info = await this.stat(fileId)\n if (!info) return new Response('Not found', { status: 404 })\n const file = this.client.file(fileId)\n return new Response(file.stream(), {\n headers: {\n 'Content-Type': info.contentType || 'application/octet-stream',\n },\n })\n }\n\n async delete(fileId: string): Promise<void> {\n await this.client.delete(fileId)\n }\n\n async exists(fileId: string): Promise<boolean> {\n return this.client.exists(fileId)\n }\n\n async presignPut(key: string, opts: PresignPutOptions): Promise<string> {\n return this.client.presign(key, {\n method: 'PUT',\n expiresIn: opts.expiresIn,\n ...(opts.contentType && { type: opts.contentType }),\n })\n }\n\n async presignGet(key: string, opts: PresignGetOptions): Promise<string> {\n return this.client.presign(key, {\n method: 'GET',\n expiresIn: opts.expiresIn,\n })\n }\n\n async stat(\n key: string,\n ): Promise<{ size: number; contentType: string } | null> {\n try {\n const s = await this.client.stat(key)\n return { size: s.size, contentType: s.type }\n } catch {\n return null\n }\n }\n\n publicUrlFor(key: string): string | undefined {\n if (!this.publicUrl) return undefined\n return `${this.publicUrl.replace(/\\/$/, '')}/${key}`\n }\n\n async list(prefix: string): Promise<string[]> {\n const res = await this.client.list({ prefix })\n return res.contents?.map((c) => c.key) ?? []\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"s3.js","sourceRoot":"","sources":["../../src/storage/s3.ts"],"names":[],"mappings":"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","sourcesContent":["// src/storage/s3.ts — S3 on fetch + SigV4, so the adapter runs outside Bun.\nimport type {\n PresignGetOptions,\n PresignPutOptions,\n StorageAdapter,\n} from './index'\n\nimport { presignUrl, signRequest, type SigV4Credentials } from './sigv4'\n\ntype Fetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>\n\ninterface S3Config {\n bucket: string\n region: string\n accessKeyId: string\n secretAccessKey: string\n endpoint?: string\n publicUrl?: string\n /** Injected in tests; defaults to the global fetch. */\n fetch?: Fetch\n}\n\nfunction decodeXml(value: string): string {\n return value\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/&/g, '&')\n}\n\nexport class S3StorageAdapter implements StorageAdapter {\n private readonly base: string\n private readonly credentials: SigV4Credentials\n private readonly publicUrl?: string\n private readonly fetchFn: Fetch\n\n constructor(cfg: S3Config) {\n const endpoint = (\n cfg.endpoint ?? `https://s3.${cfg.region}.amazonaws.com`\n ).replace(/\\/$/, '')\n this.base = `${endpoint}/${cfg.bucket}`\n this.credentials = {\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n region: cfg.region,\n }\n this.publicUrl = cfg.publicUrl\n this.fetchFn = cfg.fetch ?? ((input, init) => globalThis.fetch(input, init))\n }\n\n private objectUrl(key: string): string {\n return `${this.base}/${key.split('/').map(encodeURIComponent).join('/')}`\n }\n\n private async send(\n method: string,\n url: string,\n body?: ArrayBuffer,\n headers: Record<string, string> = {},\n ): Promise<Response> {\n const signed = await signRequest({\n method,\n url,\n headers,\n body: body ?? null,\n credentials: this.credentials,\n })\n return this.fetchFn(url, { method, headers: signed, body })\n }\n\n async upload(\n fileId: string,\n data: Blob | ArrayBuffer,\n contentType: string,\n ): Promise<void> {\n const bytes = data instanceof Blob ? await data.arrayBuffer() : data\n const res = await this.send('PUT', this.objectUrl(fileId), bytes, {\n 'content-type': contentType,\n })\n if (!res.ok) {\n throw new Error(\n `[bunderstack] S3 upload failed (${res.status}): ${await res.text()}`,\n )\n }\n }\n\n async get(fileId: string): Promise<Response> {\n const res = await this.send('GET', this.objectUrl(fileId))\n if (res.status === 404) return new Response('Not found', { status: 404 })\n if (!res.ok) {\n throw new Error(`[bunderstack] S3 get failed (${res.status})`)\n }\n return new Response(res.body, {\n headers: {\n 'Content-Type':\n res.headers.get('content-type') || 'application/octet-stream',\n },\n })\n }\n\n async delete(fileId: string): Promise<void> {\n const res = await this.send('DELETE', this.objectUrl(fileId))\n if (!res.ok && res.status !== 404) {\n throw new Error(`[bunderstack] S3 delete failed (${res.status})`)\n }\n }\n\n async exists(fileId: string): Promise<boolean> {\n return (await this.stat(fileId)) !== null\n }\n\n async stat(\n key: string,\n ): Promise<{ size: number; contentType: string } | null> {\n const res = await this.send('HEAD', this.objectUrl(key))\n if (!res.ok) return null\n return {\n size: Number(res.headers.get('content-length') ?? 0),\n contentType: res.headers.get('content-type') ?? '',\n }\n }\n\n async list(prefix: string): Promise<string[]> {\n const keys: string[] = []\n let token: string | undefined\n do {\n const url = new URL(this.base)\n url.searchParams.set('list-type', '2')\n url.searchParams.set('prefix', prefix)\n if (token) url.searchParams.set('continuation-token', token)\n const res = await this.send('GET', url.toString())\n if (!res.ok) {\n throw new Error(`[bunderstack] S3 list failed (${res.status})`)\n }\n const xml = await res.text()\n for (const match of xml.matchAll(/<Key>([\\s\\S]*?)<\\/Key>/g)) {\n keys.push(decodeXml(match[1]!))\n }\n token = /<IsTruncated>true<\\/IsTruncated>/.test(xml)\n ? decodeXml(\n /<NextContinuationToken>([\\s\\S]*?)<\\/NextContinuationToken>/.exec(\n xml,\n )?.[1] ?? '',\n )\n : undefined\n } while (token)\n return keys\n }\n\n // The content type is not signed: confirmUpload checks the stored type.\n async presignPut(key: string, opts: PresignPutOptions): Promise<string> {\n return presignUrl({\n method: 'PUT',\n url: this.objectUrl(key),\n credentials: this.credentials,\n expiresIn: opts.expiresIn,\n })\n }\n\n async presignGet(key: string, opts: PresignGetOptions): Promise<string> {\n return presignUrl({\n method: 'GET',\n url: this.objectUrl(key),\n credentials: this.credentials,\n expiresIn: opts.expiresIn,\n })\n }\n\n publicUrlFor(key: string): string | undefined {\n if (!this.publicUrl) return undefined\n return `${this.publicUrl.replace(/\\/$/, '')}/${key}`\n }\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type SigV4Credentials = {
|
|
2
|
+
accessKeyId: string;
|
|
3
|
+
secretAccessKey: string;
|
|
4
|
+
region: string;
|
|
5
|
+
service?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function signRequest(input: {
|
|
8
|
+
method: string;
|
|
9
|
+
url: string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
body?: ArrayBuffer | Uint8Array<ArrayBuffer> | string | null;
|
|
12
|
+
credentials: SigV4Credentials;
|
|
13
|
+
date?: Date;
|
|
14
|
+
unsignedPayload?: boolean;
|
|
15
|
+
}): Promise<Record<string, string>>;
|
|
16
|
+
export declare function presignUrl(input: {
|
|
17
|
+
method: string;
|
|
18
|
+
url: string;
|
|
19
|
+
credentials: SigV4Credentials;
|
|
20
|
+
expiresIn: number;
|
|
21
|
+
date?: Date;
|
|
22
|
+
signedHeaders?: Record<string, string>;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
//# sourceMappingURL=sigv4.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigv4.d.ts","sourceRoot":"","sources":["../../src/storage/sigv4.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAiGD,wBAAsB,WAAW,CAAC,KAAK,EAAE;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,IAAI,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,MAAM,GAAG,IAAI,CAAA;IAC5D,WAAW,EAAE,gBAAgB,CAAA;IAC7B,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAgClC;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,gBAAgB,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACvC,GAAG,OAAO,CAAC,MAAM,CAAC,CAiClB"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const encoder = new TextEncoder();
|
|
2
|
+
function hex(bytes) {
|
|
3
|
+
return Array.from(new Uint8Array(bytes), (b) => b.toString(16).padStart(2, '0')).join('');
|
|
4
|
+
}
|
|
5
|
+
async function sha256Hex(data) {
|
|
6
|
+
const bytes = typeof data === 'string' ? encoder.encode(data) : data;
|
|
7
|
+
return hex(await crypto.subtle.digest('SHA-256', bytes));
|
|
8
|
+
}
|
|
9
|
+
async function hmac(key, data) {
|
|
10
|
+
const cryptoKey = await crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
11
|
+
return crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(data));
|
|
12
|
+
}
|
|
13
|
+
async function signingKey(secret, day, region, service) {
|
|
14
|
+
const kDate = await hmac(encoder.encode(`AWS4${secret}`), day);
|
|
15
|
+
const kRegion = await hmac(kDate, region);
|
|
16
|
+
const kService = await hmac(kRegion, service);
|
|
17
|
+
return hmac(kService, 'aws4_request');
|
|
18
|
+
}
|
|
19
|
+
/** RFC 3986 encoding, as SigV4 requires: only A-Z a-z 0-9 - _ . ~ stay. */
|
|
20
|
+
function uriEncode(value) {
|
|
21
|
+
return encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
22
|
+
}
|
|
23
|
+
/** S3 does not normalize paths: encode each segment once, keep the slashes. */
|
|
24
|
+
function canonicalPath(pathname) {
|
|
25
|
+
return pathname
|
|
26
|
+
.split('/')
|
|
27
|
+
.map((segment) => uriEncode(decodeURIComponent(segment)))
|
|
28
|
+
.join('/');
|
|
29
|
+
}
|
|
30
|
+
function canonicalQuery(params) {
|
|
31
|
+
return [...params.entries()]
|
|
32
|
+
.map(([k, v]) => [uriEncode(k), uriEncode(v)])
|
|
33
|
+
.sort(([a, av], [b, bv]) => a < b ? -1 : a > b ? 1 : av < bv ? -1 : av > bv ? 1 : 0)
|
|
34
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
35
|
+
.join('&');
|
|
36
|
+
}
|
|
37
|
+
function amzDates(date) {
|
|
38
|
+
const amzDate = date.toISOString().replace(/[:-]|\.\d{3}/g, '');
|
|
39
|
+
return { amzDate, day: amzDate.slice(0, 8) };
|
|
40
|
+
}
|
|
41
|
+
async function signature(credentials, day, amzDate, canonicalRequest) {
|
|
42
|
+
const service = credentials.service ?? 's3';
|
|
43
|
+
const scope = `${day}/${credentials.region}/${service}/aws4_request`;
|
|
44
|
+
const stringToSign = [
|
|
45
|
+
'AWS4-HMAC-SHA256',
|
|
46
|
+
amzDate,
|
|
47
|
+
scope,
|
|
48
|
+
await sha256Hex(canonicalRequest),
|
|
49
|
+
].join('\n');
|
|
50
|
+
const key = await signingKey(credentials.secretAccessKey, day, credentials.region, service);
|
|
51
|
+
return { scope, signature: hex(await hmac(key, stringToSign)) };
|
|
52
|
+
}
|
|
53
|
+
export async function signRequest(input) {
|
|
54
|
+
const url = new URL(input.url);
|
|
55
|
+
const { amzDate, day } = amzDates(input.date ?? new Date());
|
|
56
|
+
const payloadHash = input.unsignedPayload
|
|
57
|
+
? 'UNSIGNED-PAYLOAD'
|
|
58
|
+
: await sha256Hex(input.body ?? '');
|
|
59
|
+
const headers = {};
|
|
60
|
+
for (const [k, v] of Object.entries(input.headers ?? {})) {
|
|
61
|
+
headers[k.toLowerCase()] = v.trim();
|
|
62
|
+
}
|
|
63
|
+
headers['host'] = url.host;
|
|
64
|
+
headers['x-amz-date'] = amzDate;
|
|
65
|
+
headers['x-amz-content-sha256'] = payloadHash;
|
|
66
|
+
const names = Object.keys(headers).sort();
|
|
67
|
+
const canonicalRequest = [
|
|
68
|
+
input.method.toUpperCase(),
|
|
69
|
+
canonicalPath(url.pathname),
|
|
70
|
+
canonicalQuery(url.searchParams),
|
|
71
|
+
names.map((n) => `${n}:${headers[n]}\n`).join(''),
|
|
72
|
+
names.join(';'),
|
|
73
|
+
payloadHash,
|
|
74
|
+
].join('\n');
|
|
75
|
+
const signed = await signature(input.credentials, day, amzDate, canonicalRequest);
|
|
76
|
+
headers['authorization'] =
|
|
77
|
+
`AWS4-HMAC-SHA256 Credential=${input.credentials.accessKeyId}/${signed.scope}, ` +
|
|
78
|
+
`SignedHeaders=${names.join(';')}, Signature=${signed.signature}`;
|
|
79
|
+
return headers;
|
|
80
|
+
}
|
|
81
|
+
export async function presignUrl(input) {
|
|
82
|
+
const url = new URL(input.url);
|
|
83
|
+
const { amzDate, day } = amzDates(input.date ?? new Date());
|
|
84
|
+
const service = input.credentials.service ?? 's3';
|
|
85
|
+
const headers = { host: url.host };
|
|
86
|
+
for (const [k, v] of Object.entries(input.signedHeaders ?? {})) {
|
|
87
|
+
headers[k.toLowerCase()] = v.trim();
|
|
88
|
+
}
|
|
89
|
+
const names = Object.keys(headers).sort();
|
|
90
|
+
url.searchParams.set('X-Amz-Algorithm', 'AWS4-HMAC-SHA256');
|
|
91
|
+
url.searchParams.set('X-Amz-Credential', `${input.credentials.accessKeyId}/${day}/${input.credentials.region}/${service}/aws4_request`);
|
|
92
|
+
url.searchParams.set('X-Amz-Date', amzDate);
|
|
93
|
+
url.searchParams.set('X-Amz-Expires', String(input.expiresIn));
|
|
94
|
+
url.searchParams.set('X-Amz-SignedHeaders', names.join(';'));
|
|
95
|
+
const canonicalRequest = [
|
|
96
|
+
input.method.toUpperCase(),
|
|
97
|
+
canonicalPath(url.pathname),
|
|
98
|
+
canonicalQuery(url.searchParams),
|
|
99
|
+
names.map((n) => `${n}:${headers[n]}\n`).join(''),
|
|
100
|
+
names.join(';'),
|
|
101
|
+
'UNSIGNED-PAYLOAD',
|
|
102
|
+
].join('\n');
|
|
103
|
+
const signed = await signature(input.credentials, day, amzDate, canonicalRequest);
|
|
104
|
+
url.searchParams.set('X-Amz-Signature', signed.signature);
|
|
105
|
+
return url.toString();
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=sigv4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigv4.js","sourceRoot":"","sources":["../../src/storage/sigv4.ts"],"names":[],"mappings":"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","sourcesContent":["// src/storage/sigv4.ts — AWS Signature Version 4 on WebCrypto, so S3 access\n// and presigned URLs work in Bun, workerd, and celld without Bun.S3Client.\nexport type SigV4Credentials = {\n accessKeyId: string\n secretAccessKey: string\n region: string\n service?: string\n}\n\nconst encoder = new TextEncoder()\n\nfunction hex(bytes: ArrayBuffer): string {\n return Array.from(new Uint8Array(bytes), (b) =>\n b.toString(16).padStart(2, '0'),\n ).join('')\n}\n\nasync function sha256Hex(\n data: ArrayBuffer | Uint8Array<ArrayBuffer> | string,\n): Promise<string> {\n const bytes = typeof data === 'string' ? encoder.encode(data) : data\n return hex(await crypto.subtle.digest('SHA-256', bytes))\n}\n\nasync function hmac(\n key: ArrayBuffer | Uint8Array<ArrayBuffer>,\n data: string,\n): Promise<ArrayBuffer> {\n const cryptoKey = await crypto.subtle.importKey(\n 'raw',\n key,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n return crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(data))\n}\n\nasync function signingKey(\n secret: string,\n day: string,\n region: string,\n service: string,\n): Promise<ArrayBuffer> {\n const kDate = await hmac(encoder.encode(`AWS4${secret}`), day)\n const kRegion = await hmac(kDate, region)\n const kService = await hmac(kRegion, service)\n return hmac(kService, 'aws4_request')\n}\n\n/** RFC 3986 encoding, as SigV4 requires: only A-Z a-z 0-9 - _ . ~ stay. */\nfunction uriEncode(value: string): string {\n return encodeURIComponent(value).replace(\n /[!'()*]/g,\n (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n )\n}\n\n/** S3 does not normalize paths: encode each segment once, keep the slashes. */\nfunction canonicalPath(pathname: string): string {\n return pathname\n .split('/')\n .map((segment) => uriEncode(decodeURIComponent(segment)))\n .join('/')\n}\n\nfunction canonicalQuery(params: URLSearchParams): string {\n return [...params.entries()]\n .map(([k, v]) => [uriEncode(k), uriEncode(v)] as const)\n .sort(([a, av], [b, bv]) =>\n a < b ? -1 : a > b ? 1 : av < bv ? -1 : av > bv ? 1 : 0,\n )\n .map(([k, v]) => `${k}=${v}`)\n .join('&')\n}\n\nfunction amzDates(date: Date): { amzDate: string; day: string } {\n const amzDate = date.toISOString().replace(/[:-]|\\.\\d{3}/g, '')\n return { amzDate, day: amzDate.slice(0, 8) }\n}\n\nasync function signature(\n credentials: SigV4Credentials,\n day: string,\n amzDate: string,\n canonicalRequest: string,\n): Promise<{ scope: string; signature: string }> {\n const service = credentials.service ?? 's3'\n const scope = `${day}/${credentials.region}/${service}/aws4_request`\n const stringToSign = [\n 'AWS4-HMAC-SHA256',\n amzDate,\n scope,\n await sha256Hex(canonicalRequest),\n ].join('\\n')\n const key = await signingKey(\n credentials.secretAccessKey,\n day,\n credentials.region,\n service,\n )\n return { scope, signature: hex(await hmac(key, stringToSign)) }\n}\n\nexport async function signRequest(input: {\n method: string\n url: string\n headers?: Record<string, string>\n body?: ArrayBuffer | Uint8Array<ArrayBuffer> | string | null\n credentials: SigV4Credentials\n date?: Date\n unsignedPayload?: boolean\n}): Promise<Record<string, string>> {\n const url = new URL(input.url)\n const { amzDate, day } = amzDates(input.date ?? new Date())\n const payloadHash = input.unsignedPayload\n ? 'UNSIGNED-PAYLOAD'\n : await sha256Hex(input.body ?? '')\n const headers: Record<string, string> = {}\n for (const [k, v] of Object.entries(input.headers ?? {})) {\n headers[k.toLowerCase()] = v.trim()\n }\n headers['host'] = url.host\n headers['x-amz-date'] = amzDate\n headers['x-amz-content-sha256'] = payloadHash\n const names = Object.keys(headers).sort()\n const canonicalRequest = [\n input.method.toUpperCase(),\n canonicalPath(url.pathname),\n canonicalQuery(url.searchParams),\n names.map((n) => `${n}:${headers[n]}\\n`).join(''),\n names.join(';'),\n payloadHash,\n ].join('\\n')\n const signed = await signature(\n input.credentials,\n day,\n amzDate,\n canonicalRequest,\n )\n headers['authorization'] =\n `AWS4-HMAC-SHA256 Credential=${input.credentials.accessKeyId}/${signed.scope}, ` +\n `SignedHeaders=${names.join(';')}, Signature=${signed.signature}`\n return headers\n}\n\nexport async function presignUrl(input: {\n method: string\n url: string\n credentials: SigV4Credentials\n expiresIn: number\n date?: Date\n signedHeaders?: Record<string, string>\n}): Promise<string> {\n const url = new URL(input.url)\n const { amzDate, day } = amzDates(input.date ?? new Date())\n const service = input.credentials.service ?? 's3'\n const headers: Record<string, string> = { host: url.host }\n for (const [k, v] of Object.entries(input.signedHeaders ?? {})) {\n headers[k.toLowerCase()] = v.trim()\n }\n const names = Object.keys(headers).sort()\n url.searchParams.set('X-Amz-Algorithm', 'AWS4-HMAC-SHA256')\n url.searchParams.set(\n 'X-Amz-Credential',\n `${input.credentials.accessKeyId}/${day}/${input.credentials.region}/${service}/aws4_request`,\n )\n url.searchParams.set('X-Amz-Date', amzDate)\n url.searchParams.set('X-Amz-Expires', String(input.expiresIn))\n url.searchParams.set('X-Amz-SignedHeaders', names.join(';'))\n const canonicalRequest = [\n input.method.toUpperCase(),\n canonicalPath(url.pathname),\n canonicalQuery(url.searchParams),\n names.map((n) => `${n}:${headers[n]}\\n`).join(''),\n names.join(';'),\n 'UNSIGNED-PAYLOAD',\n ].join('\\n')\n const signed = await signature(\n input.credentials,\n day,\n amzDate,\n canonicalRequest,\n )\n url.searchParams.set('X-Amz-Signature', signed.signature)\n return url.toString()\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../../src/testing/fixture.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../../src/testing/fixture.ts"],"names":[],"mappings":"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"}
|
package/dist/testing/fixture.js
CHANGED
|
@@ -13,7 +13,6 @@ import { createTestStorage, resolveTestBuckets } from './storage.js';
|
|
|
13
13
|
const defaultTestEnv = {
|
|
14
14
|
NODE_ENV: 'test',
|
|
15
15
|
AUTH_SECRET: 'bunderstack-test-secret',
|
|
16
|
-
BUNDERSTACK_ROLE: 'web',
|
|
17
16
|
};
|
|
18
17
|
function mergeTestOptions(defaults, overrides) {
|
|
19
18
|
return {
|
|
@@ -76,8 +75,6 @@ export async function createTestApp(backend, options = {}) {
|
|
|
76
75
|
messagingAdapters: testMessaging.adapters,
|
|
77
76
|
authResolver: sessions.resolver,
|
|
78
77
|
logger,
|
|
79
|
-
forceMemoryRealtime: true,
|
|
80
|
-
backgroundAutoStart: false,
|
|
81
78
|
captureTestingHandle: (handle) => {
|
|
82
79
|
testingHandle = handle;
|
|
83
80
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture.js","sourceRoot":"","sources":["../../src/testing/fixture.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdtemp, rm } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\nimport type { BunderstackBackend } from '../backend'\nimport type { AnyBunderstackApp, BunderstackClient } from '../client/rpc-client'\nimport type { TestDatabaseStrategy } from '../database/adapter'\nimport type { TestSchemaMode } from '../provision-schema'\nimport type { TestAuth, TestIdentity } from './auth'\nimport type { TestLogs, TestLogMode } from './logs'\nimport type { TestMessagingForApp } from './messaging'\nimport type { TestStorage } from './storage'\n\nimport {\n BACKEND_INTERNALS,\n type RuntimeTestingHandle,\n} from '../backend-internals'\nimport { provisionForTest } from '../provision-schema'\nimport { createTestAuth, createTestSessionRegistry } from './auth'\nimport { testClient } from './client'\nimport { createTestDatabaseTarget } from './database'\nimport { createTestJobs, type TestJobs } from './jobs'\nimport { createTestLogs } from './logs'\nimport { createTestMessaging } from './messaging'\nimport { createTestStorage, resolveTestBuckets } from './storage'\n\nexport type TestOptions = {\n env?: Record<string, string | undefined>\n database?: {\n mode?: 'memory' | 'temporary'\n schema?: TestSchemaMode\n strategy?: TestDatabaseStrategy\n }\n logs?: TestLogMode\n}\n\nexport type TestCleanup = () => unknown | Promise<unknown>\n\nexport type TestSetup<TApp, TContext> = (\n fixture: TestFixture<TApp>,\n) => TContext | Promise<TContext>\n\nexport type TestConfigureOptions<TApp, TContext> = TestOptions & {\n setup?: TestSetup<TApp, TContext>\n}\n\nexport type TestFixture<TApp> = AsyncDisposable & {\n readonly app: TApp\n readonly auth: TestAuth\n readonly messaging: TestMessagingForApp<TApp>\n readonly jobs: TestJobs\n readonly logs: TestLogs\n readonly storage: TestStorage\n defer(cleanup: TestCleanup): void\n client(\n identity?: TestIdentity,\n ): TApp extends AnyBunderstackApp ? BunderstackClient<TApp> : never\n close(): Promise<void>\n}\n\nexport type ConfiguredTestFixture<TApp, TContext> = TestFixture<TApp> & {\n readonly context: TContext\n}\n\nexport type TestFactory<TApp, TContext> = (\n options?: TestOptions,\n) => Promise<ConfiguredTestFixture<TApp, TContext>>\n\nexport type TestMethod<TApp> = {\n (options?: TestOptions): Promise<TestFixture<TApp>>\n configure<TContext = undefined>(\n options: TestConfigureOptions<TApp, TContext>,\n ): TestFactory<TApp, TContext>\n}\n\ntype TestableApp = {\n auth: unknown\n handler(request: Request): Promise<Response>\n close(): Promise<void>\n}\n\nconst defaultTestEnv = {\n NODE_ENV: 'test',\n AUTH_SECRET: 'bunderstack-test-secret',\n BUNDERSTACK_ROLE: 'web',\n} satisfies Record<string, string | undefined>\n\nfunction mergeTestOptions(\n defaults: TestOptions,\n overrides: TestOptions,\n): TestOptions {\n return {\n ...defaults,\n ...overrides,\n env:\n defaults.env || overrides.env\n ? { ...defaults.env, ...overrides.env }\n : undefined,\n database:\n defaults.database || overrides.database\n ? { ...defaults.database, ...overrides.database }\n : undefined,\n }\n}\n\nexport function configureTestApp<\n TApp extends TestableApp,\n TContext = undefined,\n>(\n backend: BunderstackBackend<TApp>,\n options: TestConfigureOptions<TApp, TContext>,\n): TestFactory<TApp, TContext> {\n const { setup, ...defaults } = options\n return async (overrides = {}) => {\n const fixture = await createTestApp(\n backend,\n mergeTestOptions(defaults, overrides),\n )\n try {\n const context = setup ? await setup(fixture) : (undefined as TContext)\n return Object.assign(fixture, { context })\n } catch (cause) {\n try {\n await fixture.close()\n } catch (cleanupCause) {\n throw new AggregateError(\n [cause, cleanupCause],\n '[bunderstack] test fixture setup and cleanup failed',\n )\n }\n throw cause\n }\n }\n}\n\nexport async function createTestApp<TApp extends TestableApp>(\n backend: BunderstackBackend<TApp>,\n options: TestOptions = {},\n): Promise<TestFixture<TApp>> {\n const internals = backend[BACKEND_INTERNALS]\n const source = { ...defaultTestEnv, ...options.env }\n const inspected = internals.inspect(source)\n const storageRoot = await mkdtemp(join(tmpdir(), 'bunderstack-storage-'))\n const resolvedStorage = resolveTestBuckets(\n inspected.config.storage,\n storageRoot,\n )\n const testMessaging = createTestMessaging(inspected.config.messaging)\n const storage = createTestStorage(resolvedStorage)\n const sessions = createTestSessionRegistry()\n const { logger, logs } = createTestLogs(options.logs)\n\n let target\n try {\n target = await createTestDatabaseTarget(inspected.config.database.adapter, {\n mode: options.database?.mode ?? 'memory',\n strategy: options.database?.strategy,\n })\n } catch (cause) {\n await rm(storageRoot, { recursive: true, force: true })\n throw cause\n }\n\n let app: TApp | undefined\n let testingHandle: RuntimeTestingHandle | undefined\n try {\n app = await internals.start(\n source,\n {\n database: target.connection,\n resolvedStorage,\n messagingAdapters: testMessaging.adapters,\n authResolver: sessions.resolver,\n logger,\n forceMemoryRealtime: true,\n backgroundAutoStart: false,\n captureTestingHandle: (handle) => {\n testingHandle = handle\n },\n },\n inspected,\n )\n await provisionForTest(app as object, options.database?.schema ?? 'auto')\n } catch (cause) {\n const cleanupErrors: unknown[] = []\n if (app) {\n try {\n await app.close()\n } catch (error) {\n cleanupErrors.push(error)\n }\n }\n try {\n await target[Symbol.asyncDispose]()\n } catch (error) {\n cleanupErrors.push(error)\n }\n try {\n await rm(storageRoot, { recursive: true, force: true })\n } catch (error) {\n cleanupErrors.push(error)\n }\n if (cleanupErrors.length > 0) {\n throw new AggregateError(\n [cause, ...cleanupErrors],\n '[bunderstack] test fixture setup and cleanup failed',\n )\n }\n throw cause\n }\n\n if (!testingHandle) {\n await app.close()\n await target[Symbol.asyncDispose]()\n await rm(storageRoot, { recursive: true, force: true })\n throw new Error('[bunderstack] runtime did not provide test controls')\n }\n\n const auth = createTestAuth(app, testMessaging.authEmail, sessions)\n const jobs = createTestJobs(testingHandle)\n const deferred: TestCleanup[] = []\n let closePromise: Promise<void> | undefined\n const defer = (cleanup: TestCleanup) => {\n if (closePromise) {\n throw new Error('[bunderstack] cannot defer cleanup after fixture close')\n }\n deferred.push(cleanup)\n }\n const close = () => {\n closePromise ??= (async () => {\n const errors: unknown[] = []\n for (const cleanup of deferred.reverse()) {\n try {\n await cleanup()\n } catch (error) {\n errors.push(error)\n }\n }\n try {\n await app.close()\n } catch (error) {\n errors.push(error)\n }\n try {\n await target[Symbol.asyncDispose]()\n } catch (error) {\n errors.push(error)\n }\n try {\n await rm(storageRoot, { recursive: true, force: true })\n } catch (error) {\n errors.push(error)\n }\n if (errors.length === 1) throw errors[0]\n if (errors.length > 1) {\n throw new AggregateError(errors, '[bunderstack] fixture cleanup failed')\n }\n })()\n return closePromise\n }\n\n return {\n app,\n auth,\n messaging: testMessaging.messaging as TestMessagingForApp<TApp>,\n jobs,\n logs,\n storage,\n defer,\n client: (identity) => testClient(app as never, identity) as never,\n close,\n [Symbol.asyncDispose]: close,\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fixture.js","sourceRoot":"","sources":["../../src/testing/fixture.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdtemp, rm } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\nimport type { BunderstackBackend } from '../backend'\nimport type { AnyBunderstackApp, BunderstackClient } from '../client/rpc-client'\nimport type { TestDatabaseStrategy } from '../database/adapter'\nimport type { TestSchemaMode } from '../provision-schema'\nimport type { TestAuth, TestIdentity } from './auth'\nimport type { TestLogs, TestLogMode } from './logs'\nimport type { TestMessagingForApp } from './messaging'\nimport type { TestStorage } from './storage'\n\nimport {\n BACKEND_INTERNALS,\n type RuntimeTestingHandle,\n} from '../backend-internals'\nimport { provisionForTest } from '../provision-schema'\nimport { createTestAuth, createTestSessionRegistry } from './auth'\nimport { testClient } from './client'\nimport { createTestDatabaseTarget } from './database'\nimport { createTestJobs, type TestJobs } from './jobs'\nimport { createTestLogs } from './logs'\nimport { createTestMessaging } from './messaging'\nimport { createTestStorage, resolveTestBuckets } from './storage'\n\nexport type TestOptions = {\n env?: Record<string, string | undefined>\n database?: {\n mode?: 'memory' | 'temporary'\n schema?: TestSchemaMode\n strategy?: TestDatabaseStrategy\n }\n logs?: TestLogMode\n}\n\nexport type TestCleanup = () => unknown | Promise<unknown>\n\nexport type TestSetup<TApp, TContext> = (\n fixture: TestFixture<TApp>,\n) => TContext | Promise<TContext>\n\nexport type TestConfigureOptions<TApp, TContext> = TestOptions & {\n setup?: TestSetup<TApp, TContext>\n}\n\nexport type TestFixture<TApp> = AsyncDisposable & {\n readonly app: TApp\n readonly auth: TestAuth\n readonly messaging: TestMessagingForApp<TApp>\n readonly jobs: TestJobs\n readonly logs: TestLogs\n readonly storage: TestStorage\n defer(cleanup: TestCleanup): void\n client(\n identity?: TestIdentity,\n ): TApp extends AnyBunderstackApp ? BunderstackClient<TApp> : never\n close(): Promise<void>\n}\n\nexport type ConfiguredTestFixture<TApp, TContext> = TestFixture<TApp> & {\n readonly context: TContext\n}\n\nexport type TestFactory<TApp, TContext> = (\n options?: TestOptions,\n) => Promise<ConfiguredTestFixture<TApp, TContext>>\n\nexport type TestMethod<TApp> = {\n (options?: TestOptions): Promise<TestFixture<TApp>>\n configure<TContext = undefined>(\n options: TestConfigureOptions<TApp, TContext>,\n ): TestFactory<TApp, TContext>\n}\n\ntype TestableApp = {\n auth: unknown\n handler(request: Request): Promise<Response>\n close(): Promise<void>\n}\n\nconst defaultTestEnv = {\n NODE_ENV: 'test',\n AUTH_SECRET: 'bunderstack-test-secret',\n} satisfies Record<string, string | undefined>\n\nfunction mergeTestOptions(\n defaults: TestOptions,\n overrides: TestOptions,\n): TestOptions {\n return {\n ...defaults,\n ...overrides,\n env:\n defaults.env || overrides.env\n ? { ...defaults.env, ...overrides.env }\n : undefined,\n database:\n defaults.database || overrides.database\n ? { ...defaults.database, ...overrides.database }\n : undefined,\n }\n}\n\nexport function configureTestApp<\n TApp extends TestableApp,\n TContext = undefined,\n>(\n backend: BunderstackBackend<TApp>,\n options: TestConfigureOptions<TApp, TContext>,\n): TestFactory<TApp, TContext> {\n const { setup, ...defaults } = options\n return async (overrides = {}) => {\n const fixture = await createTestApp(\n backend,\n mergeTestOptions(defaults, overrides),\n )\n try {\n const context = setup ? await setup(fixture) : (undefined as TContext)\n return Object.assign(fixture, { context })\n } catch (cause) {\n try {\n await fixture.close()\n } catch (cleanupCause) {\n throw new AggregateError(\n [cause, cleanupCause],\n '[bunderstack] test fixture setup and cleanup failed',\n )\n }\n throw cause\n }\n }\n}\n\nexport async function createTestApp<TApp extends TestableApp>(\n backend: BunderstackBackend<TApp>,\n options: TestOptions = {},\n): Promise<TestFixture<TApp>> {\n const internals = backend[BACKEND_INTERNALS]\n const source = { ...defaultTestEnv, ...options.env }\n const inspected = internals.inspect(source)\n const storageRoot = await mkdtemp(join(tmpdir(), 'bunderstack-storage-'))\n const resolvedStorage = resolveTestBuckets(\n inspected.config.storage,\n storageRoot,\n )\n const testMessaging = createTestMessaging(inspected.config.messaging)\n const storage = createTestStorage(resolvedStorage)\n const sessions = createTestSessionRegistry()\n const { logger, logs } = createTestLogs(options.logs)\n\n let target\n try {\n target = await createTestDatabaseTarget(inspected.config.database.adapter, {\n mode: options.database?.mode ?? 'memory',\n strategy: options.database?.strategy,\n })\n } catch (cause) {\n await rm(storageRoot, { recursive: true, force: true })\n throw cause\n }\n\n let app: TApp | undefined\n let testingHandle: RuntimeTestingHandle | undefined\n try {\n app = await internals.start(\n source,\n {\n database: target.connection,\n resolvedStorage,\n messagingAdapters: testMessaging.adapters,\n authResolver: sessions.resolver,\n logger,\n captureTestingHandle: (handle) => {\n testingHandle = handle\n },\n },\n inspected,\n )\n await provisionForTest(app as object, options.database?.schema ?? 'auto')\n } catch (cause) {\n const cleanupErrors: unknown[] = []\n if (app) {\n try {\n await app.close()\n } catch (error) {\n cleanupErrors.push(error)\n }\n }\n try {\n await target[Symbol.asyncDispose]()\n } catch (error) {\n cleanupErrors.push(error)\n }\n try {\n await rm(storageRoot, { recursive: true, force: true })\n } catch (error) {\n cleanupErrors.push(error)\n }\n if (cleanupErrors.length > 0) {\n throw new AggregateError(\n [cause, ...cleanupErrors],\n '[bunderstack] test fixture setup and cleanup failed',\n )\n }\n throw cause\n }\n\n if (!testingHandle) {\n await app.close()\n await target[Symbol.asyncDispose]()\n await rm(storageRoot, { recursive: true, force: true })\n throw new Error('[bunderstack] runtime did not provide test controls')\n }\n\n const auth = createTestAuth(app, testMessaging.authEmail, sessions)\n const jobs = createTestJobs(testingHandle)\n const deferred: TestCleanup[] = []\n let closePromise: Promise<void> | undefined\n const defer = (cleanup: TestCleanup) => {\n if (closePromise) {\n throw new Error('[bunderstack] cannot defer cleanup after fixture close')\n }\n deferred.push(cleanup)\n }\n const close = () => {\n closePromise ??= (async () => {\n const errors: unknown[] = []\n for (const cleanup of deferred.reverse()) {\n try {\n await cleanup()\n } catch (error) {\n errors.push(error)\n }\n }\n try {\n await app.close()\n } catch (error) {\n errors.push(error)\n }\n try {\n await target[Symbol.asyncDispose]()\n } catch (error) {\n errors.push(error)\n }\n try {\n await rm(storageRoot, { recursive: true, force: true })\n } catch (error) {\n errors.push(error)\n }\n if (errors.length === 1) throw errors[0]\n if (errors.length > 1) {\n throw new AggregateError(errors, '[bunderstack] fixture cleanup failed')\n }\n })()\n return closePromise\n }\n\n return {\n app,\n auth,\n messaging: testMessaging.messaging as TestMessagingForApp<TApp>,\n jobs,\n logs,\n storage,\n defer,\n client: (identity) => testClient(app as never, identity) as never,\n close,\n [Symbol.asyncDispose]: close,\n }\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DurableObjectNamespaceLike, DurableObjectStateLike, R2BucketLike } from '../workers/types.js';
|
|
2
|
+
export declare function createFakeState(): DurableObjectStateLike & {
|
|
3
|
+
alarmAt(): number | null;
|
|
4
|
+
};
|
|
5
|
+
export declare function createFakeNamespace<T extends {
|
|
6
|
+
fetch(request: Request): Promise<Response>;
|
|
7
|
+
}>(make: (state: DurableObjectStateLike, name: string) => T): DurableObjectNamespaceLike & {
|
|
8
|
+
instance(name: string): T;
|
|
9
|
+
state(name: string): ReturnType<typeof createFakeState>;
|
|
10
|
+
};
|
|
11
|
+
export declare function createFakeR2(): R2BucketLike & {
|
|
12
|
+
objects: Map<string, {
|
|
13
|
+
bytes: Uint8Array;
|
|
14
|
+
contentType?: string;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=workers-fakes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workers-fakes.d.ts","sourceRoot":"","sources":["../../src/testing/workers-fakes.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,0BAA0B,EAC1B,sBAAsB,EACtB,YAAY,EAEb,MAAM,kBAAkB,CAAA;AAEzB,wBAAgB,eAAe;eAEsB,MAAM,GAAG,IAAI;EAejE;AAED,wBAAgB,mBAAmB,CACjC,CAAC,SAAS;IAAE,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CAAE,EACxD,IAAI,EAAE,CAAC,KAAK,EAAE,sBAAsB,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;mBAevC,MAAM,GAAG,CAAC;gBACb,MAAM,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC;EAW1D;AAED,wBAAgB,YAAY;;eACe,UAAU;sBAAgB,MAAM;;EAwD1E"}
|