feature: add support for dynamoDB for storing sessions and some types cleanup (#12140)

This commit is contained in:
Harminder Virk
2025-04-16 14:55:14 +05:30
committed by GitHub
parent fe48f825f8
commit b890263725
13 changed files with 1792 additions and 1053 deletions

View File

@@ -145,6 +145,7 @@ describe("defineConfig", function () {
"redisOptions": {
"retryStrategy": [Function],
},
"sessionOptions": {},
},
}
`)
@@ -304,6 +305,7 @@ describe("defineConfig", function () {
"redisOptions": {
"retryStrategy": [Function],
},
"sessionOptions": {},
},
}
`)
@@ -471,6 +473,7 @@ describe("defineConfig", function () {
"redisOptions": {
"retryStrategy": [Function],
},
"sessionOptions": {},
},
}
`)
@@ -639,6 +642,7 @@ describe("defineConfig", function () {
"redisOptions": {
"retryStrategy": [Function],
},
"sessionOptions": {},
},
}
`)
@@ -795,6 +799,7 @@ describe("defineConfig", function () {
"redisOptions": {
"retryStrategy": [Function],
},
"sessionOptions": {},
},
}
`)
@@ -954,6 +959,7 @@ describe("defineConfig", function () {
"redisOptions": {
"retryStrategy": [Function],
},
"sessionOptions": {},
},
}
`)
@@ -1147,6 +1153,599 @@ describe("defineConfig", function () {
"retryStrategy": [Function],
},
"redisUrl": "redis://localhost:6379",
"sessionOptions": {},
},
}
`)
})
it("should include cloud-based config with dynamo db", function () {
const originalEnv = { ...process.env }
process.env.EXECUTION_CONTEXT = "medusa-cloud"
process.env.REDIS_URL = "redis://localhost:6379"
process.env.S3_FILE_URL = "https://s3.amazonaws.com/medusa-cloud-test"
process.env.S3_PREFIX = "test"
process.env.S3_REGION = "us-east-1"
process.env.S3_BUCKET = "medusa-cloud-test"
process.env.S3_ENDPOINT = "https://s3.amazonaws.com"
process.env.SESSION_STORE = "dynamodb"
const res = defineConfig({})
process.env = { ...originalEnv }
expect(res).toMatchInlineSnapshot(`
{
"admin": {
"backendUrl": "/",
"path": "/app",
},
"featureFlags": {},
"modules": {
"api_key": {
"resolve": "@medusajs/medusa/api-key",
},
"auth": {
"options": {
"providers": [
{
"id": "emailpass",
"resolve": "@medusajs/medusa/auth-emailpass",
},
],
},
"resolve": "@medusajs/medusa/auth",
},
"cache": {
"options": {
"redisUrl": "redis://localhost:6379",
},
"resolve": "@medusajs/medusa/cache-redis",
},
"cart": {
"resolve": "@medusajs/medusa/cart",
},
"currency": {
"resolve": "@medusajs/medusa/currency",
},
"customer": {
"resolve": "@medusajs/medusa/customer",
},
"event_bus": {
"options": {
"redisUrl": "redis://localhost:6379",
},
"resolve": "@medusajs/medusa/event-bus-redis",
},
"file": {
"options": {
"providers": [
{
"id": "s3",
"options": {
"authentication_method": "s3-iam-role",
"bucket": "medusa-cloud-test",
"endpoint": "https://s3.amazonaws.com",
"file_url": "https://s3.amazonaws.com/medusa-cloud-test",
"prefix": "test",
"region": "us-east-1",
},
"resolve": "@medusajs/medusa/file-s3",
},
],
},
"resolve": "@medusajs/medusa/file",
},
"fulfillment": {
"options": {
"providers": [
{
"id": "manual",
"resolve": "@medusajs/medusa/fulfillment-manual",
},
],
},
"resolve": "@medusajs/medusa/fulfillment",
},
"inventory": {
"resolve": "@medusajs/medusa/inventory",
},
"locking": {
"options": {
"providers": [
{
"id": "locking-redis",
"is_default": true,
"options": {
"redisUrl": "redis://localhost:6379",
},
"resolve": "@medusajs/medusa/locking-redis",
},
],
},
"resolve": "@medusajs/medusa/locking",
},
"notification": {
"options": {
"providers": [
{
"id": "local",
"options": {
"channels": [
"feed",
],
"name": "Local Notification Provider",
},
"resolve": "@medusajs/medusa/notification-local",
},
],
},
"resolve": "@medusajs/medusa/notification",
},
"order": {
"resolve": "@medusajs/medusa/order",
},
"payment": {
"resolve": "@medusajs/medusa/payment",
},
"pricing": {
"resolve": "@medusajs/medusa/pricing",
},
"product": {
"resolve": "@medusajs/medusa/product",
},
"promotion": {
"resolve": "@medusajs/medusa/promotion",
},
"region": {
"resolve": "@medusajs/medusa/region",
},
"sales_channel": {
"resolve": "@medusajs/medusa/sales-channel",
},
"stock_location": {
"resolve": "@medusajs/medusa/stock-location",
},
"store": {
"resolve": "@medusajs/medusa/store",
},
"tax": {
"resolve": "@medusajs/medusa/tax",
},
"user": {
"options": {
"jwt_secret": "supersecret",
},
"resolve": "@medusajs/medusa/user",
},
"workflows": {
"options": {
"redis": {
"url": "redis://localhost:6379",
},
},
"resolve": "@medusajs/medusa/workflow-engine-redis",
},
},
"plugins": [],
"projectConfig": {
"databaseUrl": "postgres://localhost/medusa-starter-default",
"http": {
"adminCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173",
"authCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173",
"cookieSecret": "supersecret",
"jwtSecret": "supersecret",
"restrictedFields": {
"store": [
${DEFAULT_STORE_RESTRICTED_FIELDS.map((v) => `"${v}"`).join(
",\n "
)},
],
},
"storeCors": "http://localhost:8000",
},
"redisOptions": {
"retryStrategy": [Function],
},
"redisUrl": "redis://localhost:6379",
"sessionOptions": {
"dynamodbOptions": {
"hashKey": "id",
"initialized": true,
"prefix": "sess:",
"readCapacityUnits": 5,
"skipThrowMissingSpecialKeys": true,
"table": "medusa-sessions",
"writeCapacityUnits": 5,
},
},
},
}
`)
})
it("should allow overriding cloud-only dynamodb config values via environment variables", function () {
const originalEnv = { ...process.env }
process.env.EXECUTION_CONTEXT = "medusa-cloud"
process.env.REDIS_URL = "redis://localhost:6379"
process.env.S3_FILE_URL = "https://s3.amazonaws.com/medusa-cloud-test"
process.env.S3_PREFIX = "test"
process.env.S3_REGION = "us-east-1"
process.env.S3_BUCKET = "medusa-cloud-test"
process.env.S3_ENDPOINT = "https://s3.amazonaws.com"
process.env.SESSION_STORE = "dynamodb"
process.env.DYNAMO_DB_SESSIONS_CREATE_TABLE = "true"
process.env.DYNAMO_DB_SESSIONS_HASH_KEY = "user_id"
process.env.DYNAMO_DB_SESSIONS_PREFIX = "my_session:"
process.env.DYNAMO_DB_SESSIONS_TABLE = "test-sessions"
process.env.DYNAMO_DB_SESSIONS_READ_UNITS = "10"
process.env.DYNAMO_DB_SESSIONS_WRITE_UNITS = "10"
const res = defineConfig({})
process.env = { ...originalEnv }
expect(res).toMatchInlineSnapshot(`
{
"admin": {
"backendUrl": "/",
"path": "/app",
},
"featureFlags": {},
"modules": {
"api_key": {
"resolve": "@medusajs/medusa/api-key",
},
"auth": {
"options": {
"providers": [
{
"id": "emailpass",
"resolve": "@medusajs/medusa/auth-emailpass",
},
],
},
"resolve": "@medusajs/medusa/auth",
},
"cache": {
"options": {
"redisUrl": "redis://localhost:6379",
},
"resolve": "@medusajs/medusa/cache-redis",
},
"cart": {
"resolve": "@medusajs/medusa/cart",
},
"currency": {
"resolve": "@medusajs/medusa/currency",
},
"customer": {
"resolve": "@medusajs/medusa/customer",
},
"event_bus": {
"options": {
"redisUrl": "redis://localhost:6379",
},
"resolve": "@medusajs/medusa/event-bus-redis",
},
"file": {
"options": {
"providers": [
{
"id": "s3",
"options": {
"authentication_method": "s3-iam-role",
"bucket": "medusa-cloud-test",
"endpoint": "https://s3.amazonaws.com",
"file_url": "https://s3.amazonaws.com/medusa-cloud-test",
"prefix": "test",
"region": "us-east-1",
},
"resolve": "@medusajs/medusa/file-s3",
},
],
},
"resolve": "@medusajs/medusa/file",
},
"fulfillment": {
"options": {
"providers": [
{
"id": "manual",
"resolve": "@medusajs/medusa/fulfillment-manual",
},
],
},
"resolve": "@medusajs/medusa/fulfillment",
},
"inventory": {
"resolve": "@medusajs/medusa/inventory",
},
"locking": {
"options": {
"providers": [
{
"id": "locking-redis",
"is_default": true,
"options": {
"redisUrl": "redis://localhost:6379",
},
"resolve": "@medusajs/medusa/locking-redis",
},
],
},
"resolve": "@medusajs/medusa/locking",
},
"notification": {
"options": {
"providers": [
{
"id": "local",
"options": {
"channels": [
"feed",
],
"name": "Local Notification Provider",
},
"resolve": "@medusajs/medusa/notification-local",
},
],
},
"resolve": "@medusajs/medusa/notification",
},
"order": {
"resolve": "@medusajs/medusa/order",
},
"payment": {
"resolve": "@medusajs/medusa/payment",
},
"pricing": {
"resolve": "@medusajs/medusa/pricing",
},
"product": {
"resolve": "@medusajs/medusa/product",
},
"promotion": {
"resolve": "@medusajs/medusa/promotion",
},
"region": {
"resolve": "@medusajs/medusa/region",
},
"sales_channel": {
"resolve": "@medusajs/medusa/sales-channel",
},
"stock_location": {
"resolve": "@medusajs/medusa/stock-location",
},
"store": {
"resolve": "@medusajs/medusa/store",
},
"tax": {
"resolve": "@medusajs/medusa/tax",
},
"user": {
"options": {
"jwt_secret": "supersecret",
},
"resolve": "@medusajs/medusa/user",
},
"workflows": {
"options": {
"redis": {
"url": "redis://localhost:6379",
},
},
"resolve": "@medusajs/medusa/workflow-engine-redis",
},
},
"plugins": [],
"projectConfig": {
"databaseUrl": "postgres://localhost/medusa-starter-default",
"http": {
"adminCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173",
"authCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173",
"cookieSecret": "supersecret",
"jwtSecret": "supersecret",
"restrictedFields": {
"store": [
${DEFAULT_STORE_RESTRICTED_FIELDS.map((v) => `"${v}"`).join(
",\n "
)},
],
},
"storeCors": "http://localhost:8000",
},
"redisOptions": {
"retryStrategy": [Function],
},
"redisUrl": "redis://localhost:6379",
"sessionOptions": {
"dynamodbOptions": {
"hashKey": "user_id",
"initialized": false,
"prefix": "my_session:",
"readCapacityUnits": 10,
"skipThrowMissingSpecialKeys": true,
"table": "test-sessions",
"writeCapacityUnits": 10,
},
},
},
}
`)
})
it("should allow custom dynamodb config", function () {
expect(
defineConfig({
projectConfig: {
http: {
adminCors: "http://localhost:3000",
} as any,
sessionOptions: {
dynamodbOptions: {
clientOptions: {
endpoint: "http://localhost:8000",
},
table: "medusa-sessions",
writeCapacityUnits: 25,
readCapacityUnits: 25,
},
},
},
})
).toMatchInlineSnapshot(`
{
"admin": {
"backendUrl": "/",
"path": "/app",
},
"featureFlags": {},
"modules": {
"api_key": {
"resolve": "@medusajs/medusa/api-key",
},
"auth": {
"options": {
"providers": [
{
"id": "emailpass",
"resolve": "@medusajs/medusa/auth-emailpass",
},
],
},
"resolve": "@medusajs/medusa/auth",
},
"cache": {
"resolve": "@medusajs/medusa/cache-inmemory",
},
"cart": {
"resolve": "@medusajs/medusa/cart",
},
"currency": {
"resolve": "@medusajs/medusa/currency",
},
"customer": {
"resolve": "@medusajs/medusa/customer",
},
"event_bus": {
"resolve": "@medusajs/medusa/event-bus-local",
},
"file": {
"options": {
"providers": [
{
"id": "local",
"resolve": "@medusajs/medusa/file-local",
},
],
},
"resolve": "@medusajs/medusa/file",
},
"fulfillment": {
"options": {
"providers": [
{
"id": "manual",
"resolve": "@medusajs/medusa/fulfillment-manual",
},
],
},
"resolve": "@medusajs/medusa/fulfillment",
},
"inventory": {
"resolve": "@medusajs/medusa/inventory",
},
"locking": {
"resolve": "@medusajs/medusa/locking",
},
"notification": {
"options": {
"providers": [
{
"id": "local",
"options": {
"channels": [
"feed",
],
"name": "Local Notification Provider",
},
"resolve": "@medusajs/medusa/notification-local",
},
],
},
"resolve": "@medusajs/medusa/notification",
},
"order": {
"resolve": "@medusajs/medusa/order",
},
"payment": {
"resolve": "@medusajs/medusa/payment",
},
"pricing": {
"resolve": "@medusajs/medusa/pricing",
},
"product": {
"resolve": "@medusajs/medusa/product",
},
"promotion": {
"resolve": "@medusajs/medusa/promotion",
},
"region": {
"resolve": "@medusajs/medusa/region",
},
"sales_channel": {
"resolve": "@medusajs/medusa/sales-channel",
},
"stock_location": {
"resolve": "@medusajs/medusa/stock-location",
},
"store": {
"resolve": "@medusajs/medusa/store",
},
"tax": {
"resolve": "@medusajs/medusa/tax",
},
"user": {
"options": {
"jwt_secret": "supersecret",
},
"resolve": "@medusajs/medusa/user",
},
"workflows": {
"resolve": "@medusajs/medusa/workflow-engine-inmemory",
},
},
"plugins": [],
"projectConfig": {
"databaseUrl": "postgres://localhost/medusa-starter-default",
"http": {
"adminCors": "http://localhost:3000",
"authCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173",
"cookieSecret": "supersecret",
"jwtSecret": "supersecret",
"restrictedFields": {
"store": [
${DEFAULT_STORE_RESTRICTED_FIELDS.map((v) => `"${v}"`).join(
",\n "
)},
],
},
"storeCors": "http://localhost:8000",
},
"redisOptions": {
"retryStrategy": [Function],
},
"sessionOptions": {
"dynamodbOptions": {
"clientOptions": {
"endpoint": "http://localhost:8000",
},
"readCapacityUnits": 25,
"table": "medusa-sessions",
"writeCapacityUnits": 25,
},
},
},
}
`)

View File

@@ -12,6 +12,7 @@ import {
} from "../modules-sdk"
import { isObject } from "./is-object"
import { isString } from "./is-string"
import { tryConvertToNumber } from "./try-convert-to-number"
import { normalizeImportPathWithSource } from "./normalize-import-path-with-source"
import { resolveExports } from "./resolve-exports"
@@ -310,7 +311,8 @@ function normalizeProjectConfig(
projectConfig: InputConfig["projectConfig"],
{ isCloud }: { isCloud: boolean }
): ConfigModule["projectConfig"] {
const { http, redisOptions, ...restOfProjectConfig } = projectConfig || {}
const { http, redisOptions, sessionOptions, ...restOfProjectConfig } =
projectConfig || {}
/**
* The defaults to use for the project config. They are shallow merged
@@ -347,8 +349,32 @@ function normalizeProjectConfig(
},
...redisOptions,
},
sessionOptions: {
...(isCloud && process.env.SESSION_STORE === "dynamodb"
? {
dynamodbOptions: {
prefix: process.env.DYNAMO_DB_SESSIONS_PREFIX ?? "sess:",
hashKey: process.env.DYNAMO_DB_SESSIONS_HASH_KEY ?? "id",
initialized: process.env.DYNAMO_DB_SESSIONS_CREATE_TABLE
? false
: true,
table: process.env.DYNAMO_DB_SESSIONS_TABLE ?? "medusa-sessions",
readCapacityUnits: tryConvertToNumber(
process.env.DYNAMO_DB_SESSIONS_READ_UNITS,
5
),
writeCapacityUnits: tryConvertToNumber(
process.env.DYNAMO_DB_SESSIONS_WRITE_UNITS,
5
),
skipThrowMissingSpecialKeys: true,
},
}
: {}),
...sessionOptions,
},
...restOfProjectConfig,
}
} satisfies ConfigModule["projectConfig"]
}
function normalizeAdminConfig(

View File

@@ -80,6 +80,7 @@ export * from "./to-handle"
export * from "./to-kebab-case"
export * from "./to-pascal-case"
export * from "./to-unix-slash"
export * from "./try-convert-to-number"
export * from "./trim-zeros"
export * from "./unflatten-object-keys"
export * from "./upper-case-first"

View File

@@ -0,0 +1,18 @@
/**
* Transforms a value to number or returns the default value
* when original value cannot be casted to number
*/
export function tryConvertToNumber(value: unknown): number | undefined
export function tryConvertToNumber<T>(
value: unknown,
defaultValue: T
): number | T
export function tryConvertToNumber<T>(
value: unknown,
defaultValue?: T
): number | undefined | T {
const transformedValue = Number(value)
return Number.isNaN(transformedValue)
? defaultValue ?? undefined
: transformedValue
}