Skip to content
This repository was archived by the owner on Dec 10, 2024. It is now read-only.

refactor(nuxt): globalContext #116

Merged
merged 2 commits into from
Jan 25, 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
3 changes: 3 additions & 0 deletions packages/nuxt/src/module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { join, relative } from 'node:path'
import { writeFileSync } from 'node:fs'
import {
addServerImportsDir,
addTemplate,
createResolver,
defineNuxtModule,
Expand Down Expand Up @@ -112,6 +113,8 @@ export default defineNuxtModule<PergelOptions>({
resolver: _resolver,
})

addServerImportsDir(_resolver.resolve('./runtime/composables'))

saveNitroImports()
saveNuxtImports()

Expand Down
86 changes: 86 additions & 0 deletions packages/nuxt/src/runtime/composables/useClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { H3Event } from 'h3'
import { camelCase } from 'scule'
import type {
S3Client,
} from '@aws-sdk/client-s3'
import type { Redis } from 'ioredis'
import type { SESClient } from '@aws-sdk/client-ses'
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
import type { S3ModuleRuntimeConfig } from '../modules/S3/types'
import type { PostgresJSModuleRuntimeConfig } from '../modules/drizzle/types'
import { usePergelRuntime } from '../core/utils/usePergelRuntime'
import type { BullMQModuleRuntimeConfig } from '../modules/bullmq/types'
import type { SesModuleRuntimeConfig } from '../modules/ses/types'
import type { PergelGlobalContext } from '#pergel/types'

interface MapType {
s3?: {
client?: S3Client
}
drizzle?: {
postgressJSClient: PostgresJsDatabase
}
bullmq?: {
client?: Redis
}
ses?: {
client?: SESClient
}
}

interface RuntimeConfigType {
s3?: S3ModuleRuntimeConfig
drizzle?: PostgresJSModuleRuntimeConfig
bullmq?: BullMQModuleRuntimeConfig
ses?: SesModuleRuntimeConfig
}

type RuntimeConfigTypeKeys = keyof RuntimeConfigType

const pergelGlobalContext = new Map<string, MapType>()

export async function globalContext<T extends RuntimeConfigTypeKeys>(
data: PergelGlobalContext,
clientObject: (runtime: RuntimeConfigType[T]) => MapType,
event?: H3Event,
additionalMapValues?: object,
) {
const mergedProjectName = camelCase(`${data.moduleName}-${data.projectName}`)
let moduleData = pergelGlobalContext.get(mergedProjectName) as MapType
const { selectProject } = usePergelRuntime<RuntimeConfigType[T]>({
moduleName: data.moduleName,
projectName: data.projectName,
}, event)

if (moduleData) {
return {
selectData: moduleData,
runtime: selectProject,
}
}

const returnData = clientObject(selectProject as RuntimeConfigType[T]) as MapType[T]
if (!returnData)
throw new Error(`${data.moduleName} is not defined`)

moduleData ??= {} as MapType
moduleData = returnData as MapType

pergelGlobalContext.set(mergedProjectName, {
...returnData as MapType,
...additionalMapValues,
})

return {
selectData: moduleData,
runtime: selectProject,
}
}

export function getGlobalContextItem(this: {
projectName: string
moduleName: string
}) {
const mergedProjectName = camelCase(`${this.moduleName}-${this.projectName}`)
return pergelGlobalContext.get(mergedProjectName)
}
58 changes: 0 additions & 58 deletions packages/nuxt/src/runtime/core/useClient.ts

This file was deleted.

6 changes: 3 additions & 3 deletions packages/nuxt/src/runtime/core/utils/generateProjectName.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { firstLetterUppercase } from './utils'
import { camelCase } from 'scule'

export function generateProjectName(projectName: string, moduleName: string, customName?: string) {
return customName ? `${projectName + firstLetterUppercase(moduleName) + firstLetterUppercase(customName)}` : `${projectName + firstLetterUppercase(moduleName)}`
export function generateProjectName(projectName: string, moduleName: string) {
return camelCase(`${projectName}-${moduleName}`)
}
3 changes: 1 addition & 2 deletions packages/nuxt/src/runtime/core/utils/moduleRuntimeConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ export function generateModuleRuntimeConfig<T>(
moduleOptions: ResolvedPergelModuleOptions,
config: Record<string, any>,
publicRuntime?: boolean,
customName?: string,
) {
const projectName = moduleOptions.projectName
const moduleName = moduleOptions.moduleName

const runtimeConfig = nuxt.options.runtimeConfig

const name = generateProjectName(projectName, moduleName, customName)
const name = generateProjectName(projectName, moduleName)

if (publicRuntime) {
runtimeConfig.public[projectName] = defu(runtimeConfig.public[projectName] as any, {
Expand Down
3 changes: 1 addition & 2 deletions packages/nuxt/src/runtime/core/utils/usePergelRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ import { generateProjectName } from './generateProjectName'
import type { PergelGlobalContext } from '#pergel/types'
import { useRuntimeConfig } from '#imports'

export function usePergelRuntime<T>(data: PergelGlobalContext, event?: H3Event, customName?: string) {
export function usePergelRuntime<T>(data: PergelGlobalContext, _event?: H3Event) {
// WATCH: https://github.com/nuxt/nuxt/issues/24095
// TODO: add runtimeConfig `event` to `useRuntimeConfig`
const runtimeConfig = useRuntimeConfig()
const name = generateProjectName(
data.projectName,
data.moduleName,
customName,
)

const selectProject = runtimeConfig[name as any] as T
Expand Down
114 changes: 73 additions & 41 deletions packages/nuxt/src/runtime/modules/S3/composables/useS3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,82 +17,111 @@ import type {
_Object,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import consola from 'consola'
import type { S3ModuleRuntimeConfig } from '../types'
import { clientFunctionTemplate } from '../../../core/useClient'
import { globalContext } from '../../../composables/useClient'
import type { PartinalKey } from '../../../core/types/module'
import type { PergelGlobalContextOmitModule } from '#pergel/types'

const { clientInit } = clientFunctionTemplate<S3Client, S3ModuleRuntimeConfig>('S3')

export const pergelS3Client = clientInit

export async function useS3(
export async function usePergelS3(
this: PergelGlobalContextOmitModule,
pergel?: PergelGlobalContextOmitModule,
event?: H3Event,
) {
const _pergel = pergel || this
const context = pergel || this

if (!_pergel || !_pergel.projectName)
if (!context || !context.projectName)
throw new Error('Pergel is not defined')

const { client, runtime } = await pergelS3Client(_pergel, runtime => new S3Client({
region: runtime.region,
endpoint: runtime.endpoint,
credentials: {
accessKeyId: runtime.accessKeyId,
secretAccessKey: runtime.secretAccessKey,
},
}), event)
const { selectData, runtime } = await globalContext<'s3'>({
moduleName: 'S3',
projectName: context.projectName,
}, (runtime) => {
if (!runtime)
throw new Error('S3 is not defined')

return {
s3: {
client: new S3Client({
region: runtime.region,
endpoint: runtime.endpoint,
credentials: {
accessKeyId: runtime.accessKeyId,
secretAccessKey: runtime.secretAccessKey,
},
}),
},
}
}, event)

if (!selectData?.s3?.client || !runtime)
throw new Error('S3 is not defined')

if (!client)
consola.error('S3 is not defined')
const s3Composables = S3Composables.call({
client: selectData.s3.client,
runtime,
})

return {
...s3Composables,
client: selectData.s3.client,
}
}

async function signedUrl(object: PartinalKey<GetObjectCommandInput, 'Bucket'>, options?: Parameters<typeof getSignedUrl>[2]) {
function S3Composables(this: {
client: S3Client
runtime: S3ModuleRuntimeConfig
}) {
const signedUrl = async (
object: PartinalKey<GetObjectCommandInput, 'Bucket'>,
options?: Parameters<typeof getSignedUrl>[2],
) => {
const getFile = new GetObjectCommand({
...object,
Bucket: runtime.bucket || object.Bucket,
Bucket: this.runtime.bucket || object.Bucket,
})

return await getSignedUrl(client!, getFile, {
return await getSignedUrl(this.client, getFile, {
expiresIn: 60 * 60 * 1,
...options,
})
}

async function uploadObject(input: PartinalKey<PutObjectCommandInput, 'Bucket'>) {
const uploadObject = async (
input: PartinalKey<PutObjectCommandInput, 'Bucket'>,
) => {
const command = new PutObjectCommand({
...input,
Bucket: runtime.bucket || input.Bucket,
Bucket: this.runtime.bucket || input.Bucket,
})

return await client!.send(command)
return await this.client.send(command)
}

async function removeObject(input: PartinalKey<DeleteObjectCommandInput, 'Bucket'>) {
const removeObject = async (
input: PartinalKey<DeleteObjectCommandInput, 'Bucket'>,
) => {
const command = new DeleteObjectCommand({
...input,
Bucket: runtime.bucket || input.Bucket,
Bucket: this.runtime.bucket || input.Bucket,
})
return await client!.send(command)
return await this.client.send(command)
}

async function removeObjects(data: {
const removeObjects = async (data: {
listObjects: PartinalKey<ListObjectsCommandInput, 'Bucket'>
input: DeleteObjectsCommandInput
}) {
}) => {
const deleteParams: DeleteObjectsCommandInput = {
Bucket: runtime.bucket || data.listObjects.Bucket,
Bucket: this.runtime.bucket || data.listObjects.Bucket,
Delete: { Objects: [] },
}

const objectData = new ListObjectsCommand({
...data.listObjects,
Bucket: runtime.bucket || data.listObjects.Bucket,
Bucket: this.runtime.bucket || data.listObjects.Bucket,
})

const objects = await client!.send(objectData)
const objects = await this.client.send(objectData)

if (objects.Contents?.length) {
for await (const { Key } of objects.Contents) {
Expand All @@ -101,29 +130,32 @@ export async function useS3(
})
}

return await client!.send(new DeleteObjectsCommand(deleteParams))
return await this.client.send(new DeleteObjectsCommand(deleteParams))
}
}

async function getObject(input: PartinalKey<GetObjectCommandInput, 'Bucket'>) {
const getObject = async (
input: PartinalKey<GetObjectCommandInput, 'Bucket'>,
) => {
const command = new GetObjectCommand({
...input,
Bucket: runtime.bucket || input.Bucket,
Bucket: this.runtime.bucket || input.Bucket,
})
return await client!.send(command)
return await this.client.send(command)
}

async function listAllObjects(bucket?: string) {
const Bucket = bucket || runtime.bucket
const listAllObjects = async (
bucket?: string,
) => {
const Bucket = bucket || this.runtime.bucket
const allObjects: _Object[] = []
for await (const obj of paginateListObjectsV2({ client }, { Bucket }))
for await (const obj of paginateListObjectsV2({ client: this.client }, { Bucket }))
allObjects.push(...(obj.Contents ?? []))

return allObjects
}

return {
client,
signedUrl,
uploadObject,
removeObject,
Expand Down
Loading