Skip to content
Draft
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
1 change: 1 addition & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
dialect:
- postgres
- cockroachdb
- sqlite
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/config/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
foreignKeyConstraintSchema,
} from "../operations/shared/types";
import { buildContainerDevDatabaseConfigSchema } from "../dev/providers/container";
import { buildFileDevDatabaseConfig } from "../dev/providers/file";

const columnSchema = z.object({
type: z.string(),
Expand All @@ -29,7 +30,7 @@ const indexSchema = z.object({
});
export type IndexSchema = z.infer<typeof indexSchema>;

const dialectEnum = z.enum(["postgres", "cockroachdb"]);
const dialectEnum = z.enum(["postgres", "cockroachdb", "sqlite"]);
export type DialectEnum = z.infer<typeof dialectEnum>;

const databaseSchema = z.object({
Expand All @@ -41,6 +42,8 @@ export type DatabaseValue = z.infer<typeof databaseSchema>;
const devDatabaseSchema = z.union([
// Container-based configuration
buildContainerDevDatabaseConfigSchema({ defaultImage: "" }),
// File-based configuration
buildFileDevDatabaseConfig(),
]);
export type DevDatabaseValue = z.infer<typeof devDatabaseSchema>;

Expand Down
16 changes: 10 additions & 6 deletions packages/cli/src/dev/providers/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ export const buildContainerDevDatabaseConfigSchema = (options: {
defaultImage: string;
}) =>
z.object({
container: z.object({
image: z.string().default(options.defaultImage),
name: z.string().optional(),
}),
container: z
.object({
image: z.string().default(options.defaultImage),
name: z.string().optional(),
})
.default({
image: options.defaultImage,
}),
});
export type ContainerDevDatabaseConfig = z.infer<
ReturnType<typeof buildContainerDevDatabaseConfigSchema>
Expand Down Expand Up @@ -150,13 +154,13 @@ class ContainerDevDatabaseInstance implements DevDatabaseInstance {
if (runningContainer) {
const r = await runningContainer.inspect();
return {
type: "container",
type: "container" as const,
imageName: r.Config.Image,
containerID: r.Id,
};
}

return { type: "unavailable" };
return { type: "unavailable" as const };
}

async isAvailable(): Promise<boolean> {
Expand Down
159 changes: 159 additions & 0 deletions packages/cli/src/dev/providers/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import {
DevDatabaseProvider,
DevDatabaseInstance,
DevDatabaseManageType,
DevDatabaseStatus,
} from "../types";
import * as fs from "fs/promises";
import * as path from "path";
import * as os from "os";
import z from "zod";

const defaultName = "default";
export const buildFileDevDatabaseConfig = () =>
z.object({
file: z
.object({
name: z.string().default(defaultName),
})
.default({
name: defaultName,
}),
});
export type FileDevDatabaseConfig = z.infer<
ReturnType<typeof buildFileDevDatabaseConfig>
>;

/**
* File-based dev database provider
*
* Provides file-based or memory-based development database environments.
* Used by SQLite dialect.
*/
export class FileDevDatabaseProvider implements DevDatabaseProvider {
async setup(
config: FileDevDatabaseConfig,
manageType: DevDatabaseManageType
): Promise<DevDatabaseInstance> {
return new FileDevDatabaseInstance({
manageType: manageType,
name: config.file?.name,
});
}

async hasExisting(manageType: DevDatabaseManageType): Promise<boolean> {
if (manageType === "dev-start") {
// dev-startの固定パスファイルが存在するかチェック
const devStartPath = this.getDevStartPath();
try {
await fs.access(devStartPath);
return true;
} catch {
return false;
}
}
return false; // one-offは常に新規作成(memory)
}

private getDevStartPath(name?: string): string {
const basename = name || defaultName;
return path.join(os.tmpdir(), `kyrage___dev-${basename}.db`);
}

async cleanup(): Promise<void> {
// Clean up temporary kyrage database files
try {
const tempDir = os.tmpdir();
const files = await fs.readdir(tempDir);
const kyrageFiles = files.filter((f) => f.startsWith("kyrage___dev-"));

await Promise.allSettled(
kyrageFiles.map((f) => fs.unlink(path.join(tempDir, f)))
);
} catch {
// Ignore cleanup errors
}
}
}

/**
* File-based dev database instance
*
* Manages the lifecycle of a SQLite database file or memory database.
*/
class FileDevDatabaseInstance implements DevDatabaseInstance {
private connectionString: string | null = null;
private filePath: string | null = null;

constructor(
private options: {
manageType: DevDatabaseManageType;
name?: string;
}
) {}

async start(): Promise<void> {
if (this.options.manageType === "dev-start") {
// dev-start: 固定名の再利用可能ファイル(tmpdir内)
this.filePath = this.getDevStartPath();
this.connectionString = this.filePath;
} else {
// one-off: メモリベース(高速、自動削除)
this.connectionString = ":memory:";
}
}

async stop(): Promise<void> {
// SQLite doesn't require explicit stopping
// Connection will be closed when database client is disposed
}

async remove(): Promise<void> {
if (this.filePath) {
try {
await fs.unlink(this.filePath);
} catch {
// Ignore file removal errors
}
}
this.connectionString = null;
this.filePath = null;
}

getConnectionString(): string {
if (!this.connectionString) {
throw new Error("File database is not started");
}
return this.connectionString;
}

async getStatus(): Promise<DevDatabaseStatus> {
if (!this.connectionString) {
return { type: "unavailable" as const };
}

if (this.connectionString === ":memory:") {
return {
type: "file" as const,
filePath: ":memory:",
mode: "memory",
};
}

return {
type: "file" as const,
filePath: this.connectionString,
mode: "file",
};
}

async isAvailable(): Promise<boolean> {
const status = await this.getStatus();
return status.type === "file";
}

private getDevStartPath(): string {
const basename = this.options.name || defaultName;
return path.join(os.tmpdir(), `kyrage___dev-${basename}.db`);
}
}
2 changes: 2 additions & 0 deletions packages/cli/src/dialect/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { KyrageDialect } from "./types";
import { PostgresKyrageDialect } from "./postgres";
import { CockroachDBKyrageDialect } from "./cockroachdb";
import { DialectEnum } from "../config/loader";
import { SQLiteKyrageDialect } from "./sqlite";

const dialects = {
postgres: new PostgresKyrageDialect(),
cockroachdb: new CockroachDBKyrageDialect(),
sqlite: new SQLiteKyrageDialect(),
} as const;

export const getDialect = (dialectName: DialectEnum) => {
Expand Down
Loading
Loading