Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/fix-cosmos-serverless-create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect-app/infra": patch
---

Create missing Cosmos containers without throughput when the account is serverless (offer read/replace returns 400). Provisioned databases still get autoscale when configured.
20 changes: 18 additions & 2 deletions packages/infra/src/cosmos-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ export interface ContainerDb {

const isNotFound = (e: unknown) => typeof e === "object" && e !== null && "code" in e && e.code === 404

const isServerlessOfferError = (error: unknown): boolean => {
const code = typeof error === "object" && error !== null && "code" in error ? error.code : undefined
const message = error instanceof Error
? error.message
: typeof error === "object" && error !== null && "message" in error
? String(error.message)
: String(error)
const badRequest = code === 400 || code === "BadRequest" || code === "400"
return badRequest && message.toLowerCase().includes("not supported for serverless")
}

export const createContainerIfNotExists = async (
db: ContainerDb,
body: ContainerRequest & { id: string },
Expand All @@ -51,6 +62,11 @@ export const createContainerIfNotExists = async (
(e) => isNotFound(e) ? false : Promise.reject(e)
)
if (exists) return
const { resource: sharedOffer } = await db.readOffer()
await db.containers.createIfNotExists(sharedOffer ? body : { ...body, maxThroughput })
try {
const { resource: sharedOffer } = await db.readOffer()
await db.containers.createIfNotExists(sharedOffer ? body : { ...body, maxThroughput })
} catch (error) {
if (!isServerlessOfferError(error)) throw error
await db.containers.createIfNotExists(body)
}
}
21 changes: 20 additions & 1 deletion packages/infra/test/cosmos-container-throughput.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { type ContainerDb, createContainerIfNotExists } from "../src/cosmos-clie

const body = { id: "prefix-Orders", partitionKey: { paths: ["/_partitionKey"], version: 2 } }

const fakeDb = (opts: { readonly read: () => Promise<unknown>; readonly sharedOffer: boolean }) => {
const fakeDb = (opts: {
readonly read: () => Promise<unknown>
readonly sharedOffer: boolean
readonly offerError?: unknown
}) => {
const calls = { created: [] as Array<unknown>, offerReads: 0 }
const db: ContainerDb = {
container: () => ({ read: opts.read }),
Expand All @@ -15,6 +19,7 @@ const fakeDb = (opts: { readonly read: () => Promise<unknown>; readonly sharedOf
},
readOffer: () => {
calls.offerReads++
if (opts.offerError !== undefined) return Promise.reject(opts.offerError)
return Promise.resolve({ resource: opts.sharedOffer ? { id: "offer" } : undefined })
}
}
Expand Down Expand Up @@ -58,4 +63,18 @@ describe("createContainerIfNotExists", () => {
})
expect(calls.created).toEqual([])
})

it("creates without throughput when offer reads are rejected as serverless", async () => {
const { calls, db } = fakeDb({
read: missing,
sharedOffer: false,
offerError: {
code: 400,
message: "Reading or replacing offers is not supported for serverless accounts."
}
})
await createContainerIfNotExists(db, body, { autoscaleMaxThroughput: 1000 })
expect(calls.created).toEqual([body])
expect(calls.offerReads).toBe(1)
})
})
Loading