Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(uploads): Reorganise, change uploads package to storage #11411

Merged
merged 11 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .changesets/11411.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
- chore(uploads): Reorganise, change uploads package to storage (#11411) by @dac09

This PR does the following:

- renames `@redwoodjs/uploads` -> `@redwoodjs/storage`
- Renames `processors` -> `savers`

They now look like this:

```ts
// reads much nicer ✅
const processedInput = await saveFiles.forProfile(input)

// avatar is a File becomes a string
// processedInput.avatar =
// '/basePath/uploads/profile-avatar-01J6ACDPWAER8B1SAXPKQK5YA1.png'

const profile = await db.profile.update({
data: processedInput,
where: { id },
})
```

even though when you export it, it looks weird

```ts
// api/src/lib/uploads.ts
const { saveFiles, storagePrismaExtension } = setupStorage({
//...
})

export { saveFiles, storagePrismaExtension }
```

The naming convention is:
`for<Model>` - takes inputs in the shape of a Prisma data for that model but with Files instead of strings. The files get saved to storage, and a key/path is replaced in the value.

`inList` - does the same, but only takes an array of File, and returns an array of stored paths/keys
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ module.exports = {
'packages/babel-config/src/__tests__/__fixtures__/**/*',
'packages/codemods/**/__testfixtures__/**/*',
'packages/cli/**/__testfixtures__/**/*',
'packages/uploads/src/__tests__/prisma-client/*',
'packages/storage/src/__tests__/prisma-client/*',
],
rules: {
curly: 'error',
Expand Down
File renamed without changes.
35 changes: 17 additions & 18 deletions packages/uploads/README.md → packages/storage/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# `@redwoodjs/uploads`
# `@redwoodjs/storage`

This package houses

Expand All @@ -14,13 +14,12 @@ In `api/src/uploads.ts` - setup uploads - processors, storage and the prisma ext

```ts
// api/src/lib/uploads.ts
nua
import { setupUploads, UploadsConfig } from '@redwoodjs/storage'
import { FileSystemStorage } from '@redwoodjs/storage/FileSystemStorage'
import { UrlSigner } from '@redwoodjs/storage/UrlSigner'

import { UploadsConfig } from '@redwoodjs/uploads'
import { setupUploads } from '@redwoodjs/uploads'
import { FileSystemStorage } from '@redwoodjs/uploads/FileSystemStorage'
import { UrlSigner } from '@redwoodjs/uploads/signedUrl'

const uploadConfig: UploadsConfig = {
const uploadsConfig: UploadsConfig = {
// 👇 prisma model
profile: {
// 👇 pass in fields that are going to be File uploads
Expand All @@ -30,7 +29,7 @@ const uploadConfig: UploadsConfig = {
}

// 👇 exporting these allows you access elsewhere on the api side
export const storage = new FileSystemStorage({
export const fsStorage = new FileSystemStorage({
baseDir: './uploads',
})

Expand All @@ -40,13 +39,13 @@ export const urlSigner = new UrlSigner({
endpoint: '/signedUrl',
})

const { uploadsProcessors, prismaExtension, fileListProcessor } = setupUploads(
uploadConfig,
storage,
const { saveFiles, storagePrismaExtension } = setupStorage({
uploadsConfig,
storageAdapter: fsStorage,
urlSigner,
)
})

export { uploadsProcessors, prismaExtension, fileListProcessor }
export { saveFiles, storagePrismaExtension }
```

### Configuring db to use the prisma extension
Expand All @@ -59,7 +58,7 @@ import { PrismaClient } from '@prisma/client'
import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger'

import { logger } from './logger'
import { prismaExtension } from './uploads'
import { storagePrismaExtension } from './uploads'

// 👇 Notice here we create prisma client, and don't export it yet
export const prismaClient = new PrismaClient({
Expand All @@ -73,7 +72,7 @@ handlePrismaLogging({
})

// 👇 Export db after adding uploads extension
export const db = prismaClient.$extends(prismaExtension)
export const db = prismaClient.$extends(storagePrismaExtension)
```

## Using Prisma extension
Expand All @@ -99,9 +98,9 @@ export const profile: QueryResolvers['profile'] = async ({ id }) => {
}
```

## Using processors
## Using `saveFiles`

In your services, you can use the preconfigured "processors" to convert Files to strings for Prisma to save into the database. The processors, and storage adapters determine where the file is saved.
In your services, you can use the preconfigured "processors" - exported as `saveFiles` to convert Files to paths on storage, for Prisma to save into the database. The processors, and storage adapters determine where the file is saved.

```ts
// api/src/services/profiles/profiles.ts
Expand All @@ -110,7 +109,7 @@ export const updateProfile: MutationResolvers['updateProfile'] = async ({
id,
input,
}) => {
const processedInput = await uploadsProcessors.processProfileUploads(input)
const processedInput = await saveFiles.forProfile(input)

// This becomes a string 👇
// The configuration on where it was saved is passed when we setup uploads in src/lib/uploads.ts
Expand Down
File renamed without changes.
File renamed without changes.
32 changes: 13 additions & 19 deletions packages/uploads/package.json → packages/storage/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "@redwoodjs/uploads",
"name": "@redwoodjs/storage",
"version": "7.0.0",
"repository": {
"type": "git",
"url": "git+https://github.com/redwoodjs/redwood.git",
"directory": "packages/uploads"
"directory": "packages/storage"
},
"license": "MIT",
"type": "module",
Expand All @@ -20,26 +20,20 @@
}
},
"./FileSystemStorage": {
"require": "./dist/cjs/FileSystemStorage.js",
"import": "./dist/FileSystemStorage.js"
"require": "./dist/cjs/adapters/FileSystemStorage/FileSystemStorage.js",
"import": "./dist/adapters/FileSystemStorage/FileSystemStorage.js"
},
"./MemoryStorage": {
"require": "./dist/cjs/MemoryStorage.js",
"import": "./dist/MemoryStorage.js"
"require": "./dist/cjs/adapters/MemoryStorage/MemoryStorage.js",
"import": "./dist/adapters/MemoryStorage/MemoryStorage.js"
},
"./signedUrl": {
"require": "./dist/cjs/signedUrls.js",
"import": "./dist/signedUrls.js"
"./BaseStorageAdapter": {
"require": "./dist/cjs/adapters/BaseStorageAdapter.js",
"import": "./dist/adapters/BaseStorageAdapter.js"
},
"./prisma": {
"require": {
"types": "./dist/cjs/prismaExtension.d.ts",
"default": "./dist/cjs/prismaExtension.js"
},
"import": {
"types": "./dist/prismaExtension.d.ts",
"default": "./dist/prismaExtension.js"
}
"./UrlSigner": {
"require": "./dist/cjs/UrlSigner.js",
"import": "./dist/UrlSigner.js"
}
},
"files": [
Expand All @@ -48,7 +42,7 @@
],
"scripts": {
"build": "yarn setup:test && tsx ./build.mts",
"build:pack": "yarn pack -o redwoodjs-uploads.tgz",
"build:pack": "yarn pack -o redwoodjs-storage.tgz",
"build:types": "tsc --build --verbose",
"build:types-cjs": "tsc --build --verbose tsconfig.types-cjs.json",
"check:attw": "tsx attw.ts",
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { describe, it, expect } from 'vitest'

import { ensurePosixPath } from '@redwoodjs/project-config'

import { createUploadProcessors } from '../createProcessors.js'
import { MemoryStorage } from '../MemoryStorage.js'
import { MemoryStorage } from '../adapters/MemoryStorage/MemoryStorage.js'
import { createUploadSavers } from '../createSavers.js'
import type { UploadsConfig } from '../prismaExtension.js'

const memStore = new MemoryStorage({
Expand All @@ -19,12 +19,16 @@ const uploadsConfig: UploadsConfig = {
},
}

describe('Create processors', () => {
const processors = createUploadProcessors(uploadsConfig, memStore)
describe('Create savers', () => {
const fileToStorage = createUploadSavers(uploadsConfig, memStore)

it('should create processors with CapitalCased model name', () => {
expect(processors.processDumboUploads).toBeDefined()
expect(processors.processDummyUploads).toBeDefined()
it('should create savers with CapitalCased model name', () => {
expect(fileToStorage.forDumbo).toBeDefined()
expect(fileToStorage.forDummy).toBeDefined()

// These are in the schema but not in the config
expect(fileToStorage.forBook).not.toBeDefined()
expect(fileToStorage.forNoUploadFields).not.toBeDefined()
})

it('Should replace file types with location strings', async () => {
Expand All @@ -37,7 +41,7 @@ describe('Create processors', () => {
}),
}

const result = await processors.processDumboUploads(data)
const result = await fileToStorage.forDumbo(data)

// Location strings in this format: {baseDir/{model}-{field}-{ulid}.{ext}
expect(ensurePosixPath(result.firstUpload)).toMatch(
Expand All @@ -63,15 +67,15 @@ describe('Create processors', () => {
}),
}

const fileNameOverrideOnly = await processors.processDummyUploads(data, {
const fileNameOverrideOnly = await fileToStorage.forDummy(data, {
fileName: 'overridden',
})

const pathOverrideOnly = await processors.processDummyUploads(data, {
const pathOverrideOnly = await fileToStorage.forDummy(data, {
path: '/bazinga',
})

const bothOverride = await processors.processDummyUploads(data, {
const bothOverride = await fileToStorage.forDummy(data, {
path: '/bazinga',
fileName: 'overridden',
})
Expand All @@ -98,14 +102,14 @@ describe('Create processors', () => {
}),
}

const noOverride = await processors.processDummyUploads(data)
const noOverride = await fileToStorage.forDummy(data)

// No extension
expect(ensurePosixPath(noOverride.uploadField)).toMatch(
/\/memory_store_basedir\/.*[^.]+$/,
)

const withOverride = await processors.processDummyUploads(data, {
const withOverride = await fileToStorage.forDummy(data, {
fileName: 'hello',
})

Expand All @@ -119,7 +123,7 @@ describe('Create processors', () => {
// Problem is - in the database world, a string[] is not a thing
// so we need a generic way of doing this
describe('FileList processing', () => {
const processors = createUploadProcessors(uploadsConfig, memStore)
const savers = createUploadSavers(uploadsConfig, memStore)

const notPrismaData = [
new File(['Hello'], 'hello.png', {
Expand All @@ -131,7 +135,7 @@ describe('FileList processing', () => {
]

it('Should handle FileLists', async () => {
const result = await processors.processFileList(notPrismaData)
const result = await savers.inList(notPrismaData)

expect(result).toHaveLength(2)

Expand All @@ -144,7 +148,7 @@ describe('FileList processing', () => {
})

it('Should handle FileLists with SaveOptions', async () => {
const result = await processors.processFileList(notPrismaData, {
const result = await savers.inList(notPrismaData, {
path: '/bazinga_not_mem_store',
})

Expand All @@ -158,7 +162,7 @@ describe('FileList processing', () => {
})

it('Should handle empty FileLists', async () => {
const promise = processors.processFileList()
const promise = savers.inList()

await expect(promise).resolves.not.toThrow()
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { describe, it, vi, expect, beforeEach, beforeAll } from 'vitest'

import { ensurePosixPath } from '@redwoodjs/project-config'

import { FileSystemStorage } from '../FileSystemStorage.js'
import { setupUploads } from '../index.js'
import { FileSystemStorage } from '../adapters/FileSystemStorage/FileSystemStorage.js'
import { setupStorage } from '../index.js'
import type { UploadsConfig } from '../prismaExtension.js'

// @MARK: use the local prisma client in the test
Expand All @@ -31,7 +31,7 @@ vi.mock('node:fs', () => ({
}))

describe('Query extensions', () => {
const uploadConfig: UploadsConfig = {
const uploadsConfig: UploadsConfig = {
dummy: {
fields: 'uploadField',
},
Expand All @@ -40,14 +40,14 @@ describe('Query extensions', () => {
},
}

const { prismaExtension, uploadsProcessors } = setupUploads(
uploadConfig,
new FileSystemStorage({
const { storagePrismaExtension, saveFiles } = setupStorage({
uploadsConfig: uploadsConfig,
storageAdapter: new FileSystemStorage({
baseDir: '/tmp',
}),
)
})

const prismaClient = new PrismaClient().$extends(prismaExtension)
const prismaClient = new PrismaClient().$extends(storagePrismaExtension)

beforeEach(() => {
vi.resetAllMocks()
Expand All @@ -59,7 +59,7 @@ describe('Query extensions', () => {

describe('create', () => {
it('create will save files', async () => {
const processedData = await uploadsProcessors.processDummyUploads({
const processedData = await saveFiles.forDummy({
uploadField: sampleFile,
})

Expand Down
Loading
Loading