bunderstack 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +31 -2
- package/src/storage/router.ts +9 -6
- package/src/trpc.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunderstack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Batteries-included backend framework for Bun: CRUD APIs, auth, file storage, realtime, tRPC, email, and validated env from a single Drizzle schema and config object.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"backend",
|
package/src/index.ts
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
import { createRealtimeBroker, buildRealtimeRouter } from './realtime/index'
|
|
59
59
|
import { createRedisRealtimeBroker } from './realtime/redis'
|
|
60
60
|
import { deleteFileWithDerivatives } from './storage/delete'
|
|
61
|
-
import { deleteFileMetaRow } from './storage/file-meta'
|
|
61
|
+
import { deleteFileMetaRow, insertReadyFile } from './storage/file-meta'
|
|
62
62
|
import { createBucketStorages } from './storage/registry'
|
|
63
63
|
import { buildBucketStorageRouter } from './storage/router'
|
|
64
64
|
import { sweepOrphans } from './storage/sweep'
|
|
@@ -115,6 +115,17 @@ export interface StorageFacade {
|
|
|
115
115
|
key: string,
|
|
116
116
|
opts?: { bucket?: string; expiresIn?: number },
|
|
117
117
|
): Promise<string>
|
|
118
|
+
/**
|
|
119
|
+
* Upload bytes as a ready file and register it in file_meta so it is
|
|
120
|
+
* accessible via `GET /api/files/<key>`. Use this for server-generated
|
|
121
|
+
* files (e.g. generated PDFs) that are not uploaded by end-users.
|
|
122
|
+
*/
|
|
123
|
+
upload(
|
|
124
|
+
key: string,
|
|
125
|
+
body: ArrayBuffer | Uint8Array,
|
|
126
|
+
contentType: string,
|
|
127
|
+
opts?: { ownerId?: string; filename?: string },
|
|
128
|
+
): Promise<void>
|
|
118
129
|
}
|
|
119
130
|
|
|
120
131
|
export type AppStartWorkerOptions = Omit<StartWorkerOptions, 'tick'>
|
|
@@ -478,7 +489,7 @@ export async function createBunderstack<
|
|
|
478
489
|
const bucketName =
|
|
479
490
|
opts.bucket ??
|
|
480
491
|
key.split('/')[0] ??
|
|
481
|
-
|
|
492
|
+
config.storage.defaultBucket
|
|
482
493
|
const adapter = registry.get(bucketName)?.adapter
|
|
483
494
|
if (adapter?.presignGet) {
|
|
484
495
|
return adapter.presignGet(key, {
|
|
@@ -487,6 +498,23 @@ export async function createBunderstack<
|
|
|
487
498
|
}
|
|
488
499
|
return `/api/files/${key}`
|
|
489
500
|
},
|
|
501
|
+
async upload(key, body, contentType, opts = {}) {
|
|
502
|
+
const bucketName = key.split('/')[0] ?? ''
|
|
503
|
+
const adapter = registry.get(bucketName)?.adapter
|
|
504
|
+
if (!adapter) throw new Error(`Unknown bucket: ${bucketName}`)
|
|
505
|
+
const u8 = body instanceof Uint8Array ? body : new Uint8Array(body)
|
|
506
|
+
const buf: ArrayBuffer = u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer
|
|
507
|
+
await adapter.upload(key, buf, contentType)
|
|
508
|
+
await insertReadyFile(db, {
|
|
509
|
+
fileId: key,
|
|
510
|
+
bucket: bucketName,
|
|
511
|
+
ownerId: opts.ownerId ?? null,
|
|
512
|
+
scopeJson: null,
|
|
513
|
+
filename: opts.filename ?? key.split('/').at(-1) ?? null,
|
|
514
|
+
contentType,
|
|
515
|
+
size: buf.byteLength,
|
|
516
|
+
})
|
|
517
|
+
},
|
|
490
518
|
}
|
|
491
519
|
const jobRunner = jobsDefs
|
|
492
520
|
? createJobRunner({
|
|
@@ -613,6 +641,7 @@ export async function createBunderstack<
|
|
|
613
641
|
email,
|
|
614
642
|
jobs,
|
|
615
643
|
realtime,
|
|
644
|
+
storage,
|
|
616
645
|
req,
|
|
617
646
|
}),
|
|
618
647
|
})
|
package/src/storage/router.ts
CHANGED
|
@@ -371,14 +371,16 @@ export function buildBucketStorageRouter(
|
|
|
371
371
|
return c.json({ fileId, url }, 200)
|
|
372
372
|
})
|
|
373
373
|
|
|
374
|
-
// ─── GET /:bucket
|
|
375
|
-
|
|
374
|
+
// ─── GET /:bucket/* ──────────────────────────────────────────────────────
|
|
375
|
+
// NOTE: wildcard captures nested paths like `adaptations/uuid/resume.pdf`.
|
|
376
|
+
router.get('/:bucket/*', async (c) => {
|
|
376
377
|
const bucketName = c.req.param('bucket')
|
|
377
378
|
const entry = registry.get(bucketName)
|
|
378
379
|
if (!entry) return apiError(c, ErrorCode.NOT_FOUND, 'Unknown bucket', 404)
|
|
379
380
|
const { bucket, adapter } = entry
|
|
380
381
|
|
|
381
|
-
const
|
|
382
|
+
const mountPrefix = c.req.routePath.replace(/\/:[^\/]+\/\*$/, '')
|
|
383
|
+
const id = c.req.path.slice(`${mountPrefix}/${bucketName}/`.length)
|
|
382
384
|
const fileId = `${bucketName}/${id}`
|
|
383
385
|
|
|
384
386
|
const row = await getFileMeta(db, fileId)
|
|
@@ -467,14 +469,15 @@ export function buildBucketStorageRouter(
|
|
|
467
469
|
return new Response(res.body, { status: res.status, headers })
|
|
468
470
|
})
|
|
469
471
|
|
|
470
|
-
// ─── DELETE /:bucket
|
|
471
|
-
router.delete('/:bucket
|
|
472
|
+
// ─── DELETE /:bucket/* ─────────────────────────────────────────────────
|
|
473
|
+
router.delete('/:bucket/*', async (c) => {
|
|
472
474
|
const bucketName = c.req.param('bucket')
|
|
473
475
|
const entry = registry.get(bucketName)
|
|
474
476
|
if (!entry) return apiError(c, ErrorCode.NOT_FOUND, 'Unknown bucket', 404)
|
|
475
477
|
const { bucket, adapter } = entry
|
|
476
478
|
|
|
477
|
-
const
|
|
479
|
+
const mountPrefix = c.req.routePath.replace(/\/:[^/]+\/\*$/, '')
|
|
480
|
+
const id = c.req.path.slice(`${mountPrefix}/${bucketName}/`.length)
|
|
478
481
|
const fileId = `${bucketName}/${id}`
|
|
479
482
|
|
|
480
483
|
const row = await getFileMeta(db, fileId)
|
package/src/trpc.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { DbFor } from './db'
|
|
|
7
7
|
import type { EmailFacade } from './email'
|
|
8
8
|
import type { JobsRuntimeFacade } from './jobs/index'
|
|
9
9
|
import type { RealtimeFacade } from './realtime/facade'
|
|
10
|
+
import type { StorageFacade } from './index'
|
|
10
11
|
|
|
11
12
|
export type TRPCContext<
|
|
12
13
|
TSchema extends Record<string, unknown>,
|
|
@@ -18,6 +19,7 @@ export type TRPCContext<
|
|
|
18
19
|
email: EmailFacade
|
|
19
20
|
jobs: JobsRuntimeFacade
|
|
20
21
|
realtime: RealtimeFacade<TSchema>
|
|
22
|
+
storage: StorageFacade
|
|
21
23
|
req: Request
|
|
22
24
|
}
|
|
23
25
|
|