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
6 changes: 6 additions & 0 deletions .changeset/quiet-dialects-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"emdash": patch
"@emdash-cms/cloudflare": patch
---

Fixes production build warnings when database adapters do not provide the optional cold-start query coalescing dialect.
2 changes: 2 additions & 0 deletions packages/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ export function d1(config: D1Config): DatabaseDescriptor {
config,
type: "sqlite",
supportsRequestScope: true,
supportsCoalescing: true,
};
}

Expand Down Expand Up @@ -370,6 +371,7 @@ export function durableObjects(config: DurableObjectsConfig): DatabaseDescriptor
config,
type: "sqlite",
supportsRequestScope: true,
supportsCoalescing: true,
};
}

Expand Down
18 changes: 17 additions & 1 deletion packages/cloudflare/tests/do-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import { describe, it, expect } from "vitest";

import { previewDatabase, playgroundDatabase } from "../src/index.js";
import { d1, durableObjects, previewDatabase, playgroundDatabase } from "../src/index.js";

describe("d1()", () => {
it("opts into request-scoped and coalescing dialects", () => {
const result = d1({ binding: "DB" });
expect(result.supportsRequestScope).toBe(true);
expect(result.supportsCoalescing).toBe(true);
});
});

describe("durableObjects()", () => {
it("opts into request-scoped and coalescing dialects", () => {
const result = durableObjects({ binding: "DB_DO" });
expect(result.supportsRequestScope).toBe(true);
expect(result.supportsCoalescing).toBe(true);
});
});

describe("previewDatabase()", () => {
it("returns a sqlite DatabaseDescriptor with the DO entrypoint", () => {
Expand Down
19 changes: 12 additions & 7 deletions packages/core/src/astro/integration/virtual-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,18 @@ export function generateConfigModule(serializableConfig: Record<string, unknown>
* the generator re-exports it so middleware can ask for a per-request Kysely
* (used for D1 Sessions API, bookmark cookies, read-replica routing). Other
* adapters get a stub that returns null.
*
* Adapters independently opt into the cold-start coalescing dialect. Keeping
* this capability explicit prevents bundlers from probing exports that do not
* exist on SQLite, libSQL, PostgreSQL, or other adapters.
*/
export function generateDialectModule(opts: {
entrypoint?: string;
type?: string;
supportsRequestScope: boolean;
supportsCoalescing: boolean;
}): string {
const { entrypoint, supportsRequestScope } = opts;
const { entrypoint, supportsRequestScope, supportsCoalescing } = opts;
if (!entrypoint) {
return [
`export const createDialect = undefined;`,
Expand All @@ -101,24 +106,24 @@ export function generateDialectModule(opts: {
}
const type = opts.type ?? "sqlite";

// Namespace access (not a named re-export) so backends that don't export
// createCoalescingDialect yield `undefined` rather than a build error.
const coalescingReExport = `import * as _dialectModule from "${entrypoint}";
export const createCoalescingDialect = _dialectModule.createCoalescingDialect;`;
const coalescingExport = supportsCoalescing
? `import { createCoalescingDialect as _createCoalescingDialect } from "${entrypoint}";
export const createCoalescingDialect = _createCoalescingDialect;`
: `export const createCoalescingDialect = undefined;`;

if (supportsRequestScope) {
return `
import { createDialect as _createDialect } from "${entrypoint}";
export { createRequestScopedDb } from "${entrypoint}";
${coalescingReExport}
${coalescingExport}
export const createDialect = _createDialect;
export const dialectType = ${JSON.stringify(type)};
`;
}

return `
import { createDialect as _createDialect } from "${entrypoint}";
${coalescingReExport}
${coalescingExport}
export const createDialect = _createDialect;
export const dialectType = ${JSON.stringify(type)};
export const createRequestScopedDb = (_opts) => null;
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/astro/integration/vite-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ export function createVirtualModulesPlugin(
entrypoint: resolvedConfig.database?.entrypoint,
type: resolvedConfig.database?.type,
supportsRequestScope: resolvedConfig.database?.supportsRequestScope ?? false,
supportsCoalescing: resolvedConfig.database?.supportsCoalescing ?? false,
});
}
// Generate a module that statically imports the configured storage
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/db/adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ export interface DatabaseDescriptor {
* the middleware takes its default (singleton) path.
*/
supportsRequestScope?: boolean;
/**
* When true, the adapter's runtime entrypoint MUST export a named
* `createCoalescingDialect` function. The runtime uses this fresh dialect
* only for its cold-start read batch.
*
* When false or absent, the virtual module exports `undefined` without
* inspecting an optional entrypoint export.
*/
supportsCoalescing?: boolean;
}

export interface SqliteConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ describe("generateConfigModule", () => {

describe("generateDialectModule", () => {
it("emits undefined createDialect and null stub when no entrypoint is configured", () => {
const out = generateDialectModule({ supportsRequestScope: false });
const out = generateDialectModule({
supportsRequestScope: false,
supportsCoalescing: false,
});
expect(out).toContain("export const createDialect = undefined");
expect(out).toContain("export const createRequestScopedDb = (_opts) => null");
});
Expand All @@ -47,9 +50,12 @@ describe("generateDialectModule", () => {
entrypoint: "some-adapter/dialect",
type: "sqlite",
supportsRequestScope: false,
supportsCoalescing: false,
});
expect(out).toContain(`import { createDialect as _createDialect } from "some-adapter/dialect"`);
expect(out).toContain("export const createRequestScopedDb = (_opts) => null");
expect(out).toContain("export const createCoalescingDialect = undefined");
expect(out).not.toContain("_dialectModule.createCoalescingDialect");
expect(out).not.toContain(`export { createRequestScopedDb } from`);
});

Expand All @@ -58,8 +64,13 @@ describe("generateDialectModule", () => {
entrypoint: "@emdash-cms/cloudflare/db/d1",
type: "sqlite",
supportsRequestScope: true,
supportsCoalescing: true,
});
expect(out).toContain(`export { createRequestScopedDb } from "@emdash-cms/cloudflare/db/d1"`);
expect(out).toContain(
`import { createCoalescingDialect as _createCoalescingDialect } from "@emdash-cms/cloudflare/db/d1"`,
);
expect(out).toContain("export const createCoalescingDialect = _createCoalescingDialect");
expect(out).not.toContain("= () => null");
expect(out).not.toContain("= (_opts) => null");
});
Expand All @@ -69,6 +80,7 @@ describe("generateDialectModule", () => {
entrypoint: "emdash/db/postgres",
type: "postgres",
supportsRequestScope: false,
supportsCoalescing: false,
});
expect(out).toContain(`export const dialectType = "postgres"`);
});
Expand Down
Loading