@effectify/prisma 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +154 -0
  2. package/dist/src/cli.d.ts +2 -0
  3. package/dist/src/cli.js +17 -0
  4. package/dist/src/commands/generate-effect.d.ts +2 -0
  5. package/dist/src/commands/generate-effect.js +73 -0
  6. package/dist/src/commands/generate-sql-schema.d.ts +2 -0
  7. package/dist/src/commands/generate-sql-schema.js +72 -0
  8. package/dist/src/commands/init.d.ts +4 -0
  9. package/dist/src/commands/init.js +102 -0
  10. package/dist/src/effect-prisma.d.ts +2 -0
  11. package/dist/src/effect-prisma.js +1771 -0
  12. package/dist/src/generators/prisma-effect-generator.d.ts +1 -0
  13. package/dist/src/generators/prisma-effect-generator.js +446 -0
  14. package/dist/src/generators/sql-schema-generator.d.ts +1 -0
  15. package/dist/src/generators/sql-schema-generator.js +58 -0
  16. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  17. package/package.json +53 -0
  18. package/prisma/dev.db +0 -0
  19. package/prisma/generated/client.d.ts +1 -0
  20. package/prisma/generated/client.js +5 -0
  21. package/prisma/generated/default.d.ts +1 -0
  22. package/prisma/generated/default.js +5 -0
  23. package/prisma/generated/edge.d.ts +1 -0
  24. package/prisma/generated/edge.js +141 -0
  25. package/prisma/generated/effect/index.ts +397 -0
  26. package/prisma/generated/effect/prisma-repository.ts +954 -0
  27. package/prisma/generated/effect/prisma-schema.ts +94 -0
  28. package/prisma/generated/effect/schemas/enums.ts +6 -0
  29. package/prisma/generated/effect/schemas/index.ts +2 -0
  30. package/prisma/generated/effect/schemas/types.ts +40 -0
  31. package/prisma/generated/index-browser.js +172 -0
  32. package/prisma/generated/index.d.ts +2360 -0
  33. package/prisma/generated/index.js +141 -0
  34. package/prisma/generated/package.json +144 -0
  35. package/prisma/generated/query_compiler_bg.js +2 -0
  36. package/prisma/generated/query_compiler_bg.wasm +0 -0
  37. package/prisma/generated/query_compiler_bg.wasm-base64.js +2 -0
  38. package/prisma/generated/runtime/client.d.ts +3180 -0
  39. package/prisma/generated/runtime/client.js +86 -0
  40. package/prisma/generated/runtime/index-browser.d.ts +87 -0
  41. package/prisma/generated/runtime/index-browser.js +6 -0
  42. package/prisma/generated/runtime/wasm-compiler-edge.js +76 -0
  43. package/prisma/generated/schema.prisma +31 -0
  44. package/prisma/generated/wasm-edge-light-loader.mjs +5 -0
  45. package/prisma/generated/wasm-worker-loader.mjs +5 -0
  46. package/prisma/migrations/20250721164420_init/migration.sql +9 -0
  47. package/prisma/migrations/20250721191716_dumb/migration.sql +49 -0
  48. package/prisma/migrations/migration_lock.toml +3 -0
  49. package/prisma/schema.prisma +31 -0
  50. package/prisma.config.ts +8 -0
  51. package/project.json +48 -0
  52. package/scripts/cleanup-tests.ts +26 -0
  53. package/scripts/generate-test-files.ts +93 -0
  54. package/setup-tests.ts +10 -0
  55. package/src/cli.tsx +23 -0
  56. package/src/commands/generate-effect.ts +109 -0
  57. package/src/commands/generate-sql-schema.ts +109 -0
  58. package/src/commands/init.ts +155 -0
  59. package/src/effect-prisma.ts +1826 -0
  60. package/src/generators/prisma-effect-generator.ts +496 -0
  61. package/src/generators/sql-schema-generator.ts +75 -0
  62. package/test/prisma-model.test.ts +340 -0
  63. package/test/utils.ts +10 -0
  64. package/tsconfig.json +20 -0
  65. package/tsconfig.lib.json +24 -0
  66. package/tsconfig.spec.json +15 -0
  67. package/vitest.config.ts +23 -0
@@ -0,0 +1,49 @@
1
+ -- CreateTable
2
+ CREATE TABLE "users" (
3
+ "id" TEXT NOT NULL PRIMARY KEY,
4
+ "email" TEXT NOT NULL,
5
+ "name" TEXT,
6
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
7
+ "updatedAt" DATETIME NOT NULL
8
+ );
9
+
10
+ -- CreateTable
11
+ CREATE TABLE "tags" (
12
+ "id" TEXT NOT NULL PRIMARY KEY,
13
+ "name" TEXT NOT NULL
14
+ );
15
+
16
+ -- CreateTable
17
+ CREATE TABLE "todo_tags" (
18
+ "todoId" TEXT NOT NULL,
19
+ "tagId" TEXT NOT NULL,
20
+
21
+ PRIMARY KEY ("todoId", "tagId"),
22
+ CONSTRAINT "todo_tags_todoId_fkey" FOREIGN KEY ("todoId") REFERENCES "todos" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
23
+ CONSTRAINT "todo_tags_tagId_fkey" FOREIGN KEY ("tagId") REFERENCES "tags" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
24
+ );
25
+
26
+ -- RedefineTables
27
+ PRAGMA defer_foreign_keys=ON;
28
+ PRAGMA foreign_keys=OFF;
29
+ CREATE TABLE "new_todos" (
30
+ "id" TEXT NOT NULL PRIMARY KEY,
31
+ "title" TEXT NOT NULL,
32
+ "description" TEXT,
33
+ "completed" BOOLEAN NOT NULL DEFAULT false,
34
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
35
+ "updatedAt" DATETIME NOT NULL,
36
+ "userId" TEXT,
37
+ CONSTRAINT "todos_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE
38
+ );
39
+ INSERT INTO "new_todos" ("completed", "createdAt", "description", "id", "title", "updatedAt") SELECT "completed", "createdAt", "description", "id", "title", "updatedAt" FROM "todos";
40
+ DROP TABLE "todos";
41
+ ALTER TABLE "new_todos" RENAME TO "todos";
42
+ PRAGMA foreign_keys=ON;
43
+ PRAGMA defer_foreign_keys=OFF;
44
+
45
+ -- CreateIndex
46
+ CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
47
+
48
+ -- CreateIndex
49
+ CREATE UNIQUE INDEX "tags_name_key" ON "tags"("name");
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (e.g., Git)
3
+ provider = "sqlite"
@@ -0,0 +1,31 @@
1
+ // This is your Prisma schema file,
2
+ // learn more about it in the docs: https://pris.ly/d/prisma-schema
3
+
4
+ generator client {
5
+ provider = "prisma-client-js"
6
+ output = "./generated"
7
+ }
8
+
9
+ generator effect_schemas {
10
+ provider = "prisma-effect-kysely"
11
+ output = "./generated/effect/schemas"
12
+ }
13
+
14
+ generator effect {
15
+ provider = "tsx ./src/effect-prisma.ts"
16
+ output = "./generated/effect"
17
+ clientImportPath = "@prisma/client"
18
+ importFileExtension = "js"
19
+ }
20
+
21
+ datasource db {
22
+ provider = "sqlite"
23
+ }
24
+
25
+ model Todo {
26
+ id Int @id @default(autoincrement())
27
+ title String
28
+ content String?
29
+ published Boolean @default(false)
30
+ authorId Int
31
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'prisma/config'
2
+
3
+ export default defineConfig({
4
+ schema: 'prisma/schema.prisma',
5
+ datasource: {
6
+ url: 'file:./prisma/dev.db',
7
+ },
8
+ })
package/project.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@effectify/prisma",
3
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/prisma/src",
5
+ "projectType": "library",
6
+ "targets": {
7
+ "build": {
8
+ "executor": "@nx/js:tsc",
9
+ "outputs": ["{options.outputPath}"],
10
+ "defaultConfiguration": "production",
11
+ "options": {
12
+ "outputPath": "packages/prisma/dist",
13
+ "main": "packages/prisma/src/cli.tsx",
14
+ "tsConfig": "packages/prisma/tsconfig.lib.json",
15
+ "format": ["esm"],
16
+ "generatePackageJson": false,
17
+ "updateBuildableProjectDepsInPackageJson": false,
18
+ "additionalEntryPoints": []
19
+ },
20
+ "configurations": {
21
+ "development": {},
22
+ "production": {}
23
+ }
24
+ },
25
+ "prisma:generate": {
26
+ "executor": "nx:run-commands",
27
+ "options": {
28
+ "command": "pnpm dlx prisma generate",
29
+ "cwd": "packages/prisma"
30
+ }
31
+ },
32
+ "dev": {
33
+ "executor": "nx:run-commands",
34
+ "options": {
35
+ "command": "tsx src/messaround.ts",
36
+ "cwd": "packages/prisma"
37
+ }
38
+ },
39
+ "cli": {
40
+ "executor": "nx:run-commands",
41
+ "options": {
42
+ "command": "pnpm dlx tsx src/cli.tsx",
43
+ "cwd": "packages/prisma"
44
+ }
45
+ }
46
+ },
47
+ "tags": ["node", "react", "prisma"]
48
+ }
@@ -0,0 +1,26 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { readdir, rmdir } from 'node:fs/promises'
3
+ import { resolve } from 'node:path'
4
+
5
+ async function cleanupTests() {
6
+ const testsDir = resolve('src/generated/tests')
7
+ const testDbsDir = resolve('src/generated/tests-dbs')
8
+
9
+ // Clean up test files
10
+ if (existsSync(testsDir)) {
11
+ await rmdir(testsDir, { recursive: true })
12
+ }
13
+
14
+ // Clean up test databases
15
+ if (existsSync(testDbsDir)) {
16
+ const files = await readdir(testDbsDir)
17
+ if (files.length > 0) {
18
+ await rmdir(testDbsDir, { recursive: true })
19
+ }
20
+ }
21
+ }
22
+
23
+ cleanupTests().catch((error) => {
24
+ console.error('❌ Cleanup failed:', error)
25
+ process.exit(1)
26
+ })
@@ -0,0 +1,93 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { mkdir, writeFile } from 'node:fs/promises'
3
+ import { resolve } from 'node:path'
4
+
5
+ // Configuration
6
+ const testFileCount = Number.parseInt(process.argv[2], 10) || 5
7
+ const testsPerFileCount = Number.parseInt(process.argv[3], 10) || 3
8
+
9
+ const generateTestFile = (fileIndex: number, testsCount: number): string => {
10
+ const testCases = Array.from({ length: testsCount }, (_, testIndex) => {
11
+ const testName = `should handle test case ${testIndex + 1}`
12
+ const todoTitle = `Generated Todo ${fileIndex}-${testIndex + 1}`
13
+ const todoDescription = `Description for test ${fileIndex}-${testIndex + 1}`
14
+
15
+ return `
16
+ const test${testIndex + 1}Effect = Effect.gen(function* () {
17
+ const prisma = yield* PrismaService
18
+ const user = yield* prisma.user.create({
19
+ data: {
20
+ email: "user${fileIndex}-${testIndex + 1}@test.com"
21
+ }
22
+ })
23
+
24
+ const created = yield* createTodo({
25
+ title: "${todoTitle}",
26
+ completed: ${Math.random() > 0.5},
27
+ description: "${todoDescription}",
28
+ userId: user.id
29
+ })
30
+
31
+ const allTodos = yield* getAllTodosForUser(user)
32
+
33
+ expect(allTodos).toHaveLength(1)
34
+ expect(allTodos[0]?.title).toBe("${todoTitle}")
35
+ expect(allTodos[0]?.userId).toBe(user.id)
36
+ }).pipe(Effect.provide(PrismaService.Default)).pipe(Effect.provide(TestPrismaLayer))
37
+
38
+ it.scoped("${testName}", () => test${testIndex + 1}Effect)`
39
+ }).join('\n\n')
40
+
41
+ return `import { expect, it } from "@effect/vitest"
42
+ import { Effect } from "effect"
43
+ import type { Todo, User } from "../../generated/prisma/index.js"
44
+ import { PrismaService } from "../../generated/effect-prisma/index.js"
45
+ import { TestPrismaLayer } from "../../services/prisma.service.js"
46
+
47
+ const createTodo = (input: Omit<Todo, "id" | "createdAt" | "updatedAt">) =>
48
+ Effect.gen(function* () {
49
+ const prisma = yield* PrismaService
50
+ return yield* prisma.todo.create({
51
+ data: input
52
+ })
53
+ })
54
+
55
+ const getAllTodosForUser = (user: Pick<User, "id">) =>
56
+ Effect.gen(function* () {
57
+ const prisma = yield* PrismaService
58
+ return yield* prisma.todo.findMany({
59
+ where: {
60
+ userId: user.id
61
+ }
62
+ })
63
+ })
64
+
65
+ ${testCases}
66
+ `
67
+ }
68
+
69
+ async function generateTestFiles() {
70
+ const testsDir = resolve('src/generated/tests')
71
+
72
+ // Create the tests directory if it doesn't exist
73
+ if (!existsSync(testsDir)) {
74
+ await mkdir(testsDir, { recursive: true })
75
+ }
76
+
77
+ // Generate test files
78
+ const filePromises: Promise<void>[] = []
79
+ for (let i = 1; i <= testFileCount; i++) {
80
+ const fileName = `stress-test-${i.toString().padStart(3, '0')}.test.ts`
81
+ const filePath = resolve(testsDir, fileName)
82
+ const fileContent = generateTestFile(i, testsPerFileCount)
83
+
84
+ filePromises.push(writeFile(filePath, fileContent, 'utf-8'))
85
+ }
86
+
87
+ await Promise.all(filePromises)
88
+ }
89
+
90
+ generateTestFiles().catch((error) => {
91
+ console.error('Failed to generate test files:', error)
92
+ process.exit(1)
93
+ })
package/setup-tests.ts ADDED
@@ -0,0 +1,10 @@
1
+ import path from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import * as it from '@effect/vitest'
4
+
5
+ it.addEqualityTesters()
6
+
7
+ if (typeof __dirname === 'undefined') {
8
+ /** biome-ignore lint/suspicious/noExplicitAny: polyfill */
9
+ ;(globalThis as any).__dirname = path.dirname(fileURLToPath(import.meta.url))
10
+ }
package/src/cli.tsx ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import * as Command from '@effect/cli/Command'
4
+ import * as NodeContext from '@effect/platform-node/NodeContext'
5
+ import * as NodeRuntime from '@effect/platform-node/NodeRuntime'
6
+ import * as Console from 'effect/Console'
7
+ import * as Effect from 'effect/Effect'
8
+ import { generateEffectCommand } from './commands/generate-effect.js'
9
+ import { generateSqlSchemaCommand } from './commands/generate-sql-schema.js'
10
+ import { initCommand } from './commands/init.js'
11
+
12
+ // Main CLI command
13
+ const prisma = Command.make('prisma', {}, () =>
14
+ Console.log('🚀 prisma CLI - Use --help to see available commands'),
15
+ ).pipe(Command.withSubcommands([initCommand, generateEffectCommand, generateSqlSchemaCommand]))
16
+
17
+ // Run the CLI
18
+ const cli = Command.run(prisma, {
19
+ name: '@effectify/prisma CLI',
20
+ version: '0.1.0',
21
+ })
22
+
23
+ cli(process.argv).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain)
@@ -0,0 +1,109 @@
1
+ import * as Command from '@effect/cli/Command'
2
+ import * as FileSystem from '@effect/platform/FileSystem'
3
+ import * as NodeFileSystem from '@effect/platform-node/NodeFileSystem'
4
+ import * as NodePath from '@effect/platform-node/NodePath'
5
+ import * as Console from 'effect/Console'
6
+ import * as Effect from 'effect/Effect'
7
+ import * as Match from 'effect/Match'
8
+
9
+ // import { generateEffectPrisma } from '../generators/prisma-effect-generator.js'
10
+
11
+ // Check if file exists
12
+ const fileExists = (path: string) =>
13
+ Effect.gen(function* () {
14
+ const fs = yield* FileSystem.FileSystem
15
+ return yield* fs.exists(path)
16
+ })
17
+
18
+ // Detect package manager using pattern matching
19
+ const detectPackageManager = () =>
20
+ Effect.gen(function* () {
21
+ const pnpmExists = yield* fileExists('pnpm-lock.yaml')
22
+ const bunExists = yield* fileExists('bun.lockb')
23
+ const npmExists = yield* fileExists('package-lock.json')
24
+
25
+ // Create a tuple to match against
26
+ const lockFiles = [pnpmExists, bunExists, npmExists] as const
27
+
28
+ return Match.value(lockFiles).pipe(
29
+ Match.when([true, false, false], () => 'pnpm' as const),
30
+ Match.when([false, true, false], () => 'bun' as const),
31
+ Match.when([false, false, true], () => 'npm' as const),
32
+ Match.orElse(() => 'npm' as const), // default fallback
33
+ )
34
+ })
35
+
36
+ // Check if Prisma schema exists
37
+ const checkPrismaSchema = () =>
38
+ Effect.gen(function* () {
39
+ const schemaExists = yield* fileExists('prisma/schema.prisma')
40
+
41
+ if (!schemaExists) {
42
+ yield* Console.log('❌ Prisma schema not found.')
43
+ yield* Console.log('')
44
+ yield* Console.log('Please run the following command first:')
45
+
46
+ const packageManager = yield* detectPackageManager()
47
+
48
+ const initCommand = Match.value(packageManager).pipe(
49
+ Match.when('pnpm', () => 'pnpm dlx prisma init'),
50
+ Match.when('bun', () => 'bunx prisma init'),
51
+ Match.when('npm', () => 'npx prisma init'),
52
+ Match.exhaustive,
53
+ )
54
+
55
+ yield* Console.log(` ${initCommand}`)
56
+ yield* Effect.fail(new Error('Prisma schema not found'))
57
+ }
58
+ })
59
+
60
+ // Execute the Effect generator
61
+ const generateEffectServices = () =>
62
+ Effect.gen(function* () {
63
+ // Check if we're being called by Prisma (no interactive output)
64
+ const isCalledByPrisma =
65
+ process.env.PRISMA_GENERATOR_INVOCATION === 'true' ||
66
+ process.argv.includes('--generator') ||
67
+ !process.stdout.isTTY
68
+
69
+ if (!isCalledByPrisma) {
70
+ yield* Console.log('🔧 Running Effect generator...')
71
+ }
72
+
73
+ // Check if Prisma schema exists
74
+ yield* checkPrismaSchema()
75
+
76
+ // Note: We don't need to check for local generator files
77
+ // since we're importing from our own package
78
+
79
+ if (!isCalledByPrisma) {
80
+ yield* Console.log('🚀 Executing Effect generator...')
81
+ }
82
+
83
+ // Execute the generator function
84
+ yield* Effect.tryPromise({
85
+ try: () => {
86
+ // Create mock options for the generator
87
+ // const mockOptions = {
88
+ // dmmf: { datamodel: { models: [] } }, // Empty models for now
89
+ // generator: { output: { value: 'src/generated/effect-prisma' } },
90
+ // }
91
+ return Promise.resolve()
92
+ // return generateEffectPrisma(mockOptions)
93
+ },
94
+ catch: (error) => new Error(`Generator execution failed: ${error}`),
95
+ })
96
+
97
+ if (!isCalledByPrisma) {
98
+ yield* Console.log('✅ Effect generator executed successfully!')
99
+ yield* Console.log('💡 Generated files are available in the configured output directory')
100
+ }
101
+
102
+ // Ensure the effect completes and exits
103
+ yield* Effect.sync(() => process.exit(0))
104
+ })
105
+
106
+ // Export the generate-effect command
107
+ export const generateEffectCommand = Command.make('generate-effect', {}, () =>
108
+ generateEffectServices().pipe(Effect.provide(NodeFileSystem.layer), Effect.provide(NodePath.layer)),
109
+ )
@@ -0,0 +1,109 @@
1
+ import * as Command from '@effect/cli/Command'
2
+ import * as FileSystem from '@effect/platform/FileSystem'
3
+ import * as NodeFileSystem from '@effect/platform-node/NodeFileSystem'
4
+ import * as NodePath from '@effect/platform-node/NodePath'
5
+ import * as Console from 'effect/Console'
6
+ import * as Effect from 'effect/Effect'
7
+ import * as Match from 'effect/Match'
8
+
9
+ // import { generateSqlSchema } from '../generators/sql-schema-generator.js'
10
+
11
+ // Check if file exists
12
+ const fileExists = (path: string) =>
13
+ Effect.gen(function* () {
14
+ const fs = yield* FileSystem.FileSystem
15
+ return yield* fs.exists(path)
16
+ })
17
+
18
+ // Detect package manager using pattern matching
19
+ const detectPackageManager = () =>
20
+ Effect.gen(function* () {
21
+ const pnpmExists = yield* fileExists('pnpm-lock.yaml')
22
+ const bunExists = yield* fileExists('bun.lockb')
23
+ const npmExists = yield* fileExists('package-lock.json')
24
+
25
+ // Create a tuple to match against
26
+ const lockFiles = [pnpmExists, bunExists, npmExists] as const
27
+
28
+ return Match.value(lockFiles).pipe(
29
+ Match.when([true, false, false], () => 'pnpm' as const),
30
+ Match.when([false, true, false], () => 'bun' as const),
31
+ Match.when([false, false, true], () => 'npm' as const),
32
+ Match.orElse(() => 'npm' as const), // default fallback
33
+ )
34
+ })
35
+
36
+ // Check if Prisma schema exists
37
+ const checkPrismaSchema = () =>
38
+ Effect.gen(function* () {
39
+ const schemaExists = yield* fileExists('prisma/schema.prisma')
40
+
41
+ if (!schemaExists) {
42
+ yield* Console.log('❌ Prisma schema not found.')
43
+ yield* Console.log('')
44
+ yield* Console.log('Please run the following command first:')
45
+
46
+ const packageManager = yield* detectPackageManager()
47
+
48
+ const initCommand = Match.value(packageManager).pipe(
49
+ Match.when('pnpm', () => 'pnpm dlx prisma init'),
50
+ Match.when('bun', () => 'bunx prisma init'),
51
+ Match.when('npm', () => 'npx prisma init'),
52
+ Match.exhaustive,
53
+ )
54
+
55
+ yield* Console.log(` ${initCommand}`)
56
+ yield* Effect.fail(new Error('Prisma schema not found'))
57
+ }
58
+ })
59
+
60
+ // Execute the SQL schema generator
61
+ const generateSqlSchemaServices = () =>
62
+ Effect.gen(function* () {
63
+ // Check if we're being called by Prisma (no interactive output)
64
+ const isCalledByPrisma =
65
+ process.env.PRISMA_GENERATOR_INVOCATION === 'true' ||
66
+ process.argv.includes('--generator') ||
67
+ !process.stdout.isTTY
68
+
69
+ if (!isCalledByPrisma) {
70
+ yield* Console.log('🔧 Running SQL schema generator...')
71
+ }
72
+
73
+ // Check if Prisma schema exists
74
+ yield* checkPrismaSchema()
75
+
76
+ // Note: We don't need to check for local generator files
77
+ // since we're importing from our own package
78
+
79
+ if (!isCalledByPrisma) {
80
+ yield* Console.log('🚀 Executing SQL schema generator...')
81
+ }
82
+
83
+ // Execute the generator function
84
+ yield* Effect.tryPromise({
85
+ try: () => {
86
+ // Create mock options for the generator
87
+ // const mockOptions = {
88
+ // generator: { output: { value: 'src/generated' } },
89
+ // }
90
+
91
+ // return generateSqlSchema(mockOptions)
92
+ return Promise.resolve()
93
+ },
94
+ catch: (error) => new Error(`Generator execution failed: ${error}`),
95
+ })
96
+
97
+ if (!isCalledByPrisma) {
98
+ yield* Console.log('✅ SQL schema generator executed successfully!')
99
+ yield* Console.log('💡 Generated files are available in the configured output directory')
100
+ }
101
+
102
+ // Ensure the effect completes and exits
103
+ yield* Effect.sync(() => process.exit(0))
104
+ })
105
+
106
+ // Export the generate-sql-schema command
107
+ export const generateSqlSchemaCommand = Command.make('generate-sql-schema', {}, () =>
108
+ generateSqlSchemaServices().pipe(Effect.provide(NodeFileSystem.layer), Effect.provide(NodePath.layer)),
109
+ )
@@ -0,0 +1,155 @@
1
+ import * as Command from '@effect/cli/Command'
2
+ import * as Options from '@effect/cli/Options'
3
+ import * as FileSystem from '@effect/platform/FileSystem'
4
+ import * as NodeFileSystem from '@effect/platform-node/NodeFileSystem'
5
+ import * as NodePath from '@effect/platform-node/NodePath'
6
+ import * as Console from 'effect/Console'
7
+ import * as Effect from 'effect/Effect'
8
+ import * as Match from 'effect/Match'
9
+
10
+ // Options for the init command
11
+ const outputOption = Options.text('output').pipe(
12
+ Options.withAlias('o'),
13
+ Options.withDescription('Output directory path for generated files'),
14
+ Options.withDefault('src'),
15
+ )
16
+
17
+ // Check if file exists
18
+ const fileExists = (path: string) =>
19
+ Effect.gen(function* () {
20
+ const fs = yield* FileSystem.FileSystem
21
+ return yield* fs.exists(path)
22
+ })
23
+
24
+ // Read file content
25
+ const readFileContent = (path: string) =>
26
+ Effect.gen(function* () {
27
+ const fs = yield* FileSystem.FileSystem
28
+ const content = yield* fs.readFileString(path)
29
+ return content
30
+ })
31
+
32
+ // Write file content
33
+ const writeFileContent = (path: string, content: string) =>
34
+ Effect.gen(function* () {
35
+ const fs = yield* FileSystem.FileSystem
36
+ yield* fs.writeFileString(path, content)
37
+ })
38
+
39
+ // Detect package manager using pattern matching
40
+ const detectPackageManager = () =>
41
+ Effect.gen(function* () {
42
+ const pnpmExists = yield* fileExists('pnpm-lock.yaml')
43
+ const bunExists = yield* fileExists('bun.lockb')
44
+ const npmExists = yield* fileExists('package-lock.json')
45
+
46
+ // Create a tuple to match against
47
+ const lockFiles = [pnpmExists, bunExists, npmExists] as const
48
+
49
+ return Match.value(lockFiles).pipe(
50
+ Match.when([true, false, false], () => 'pnpm' as const),
51
+ Match.when([false, true, false], () => 'bun' as const),
52
+ Match.when([false, false, true], () => 'npm' as const),
53
+ Match.orElse(() => 'npm' as const), // default fallback
54
+ )
55
+ })
56
+
57
+ // Check if Prisma is already initialized
58
+ const checkPrismaSetup = () =>
59
+ Effect.gen(function* () {
60
+ const schemaExists = yield* fileExists('prisma/schema.prisma')
61
+
62
+ if (!schemaExists) {
63
+ const packageManager = yield* detectPackageManager()
64
+
65
+ yield* Console.log('❌ Prisma is not initialized in this project.')
66
+ yield* Console.log('')
67
+ yield* Console.log('Please run the following command first:')
68
+
69
+ const initCommand = Match.value(packageManager).pipe(
70
+ Match.when('pnpm', () => 'pnpm dlx prisma init'),
71
+ Match.when('bun', () => 'bunx prisma init'),
72
+ Match.when('npm', () => 'npx prisma init'),
73
+ Match.exhaustive,
74
+ )
75
+
76
+ yield* Console.log(` ${initCommand}`)
77
+
78
+ yield* Console.log('')
79
+ yield* Console.log('For more information, visit:')
80
+ yield* Console.log(
81
+ ' https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases-typescript-prismaPostgres',
82
+ )
83
+ yield* Effect.fail(new Error('Prisma not initialized'))
84
+ }
85
+ })
86
+
87
+ // Initialize Prisma schema logic
88
+ const initializePrismaSchema = (options: { output: string }) =>
89
+ Effect.gen(function* () {
90
+ yield* Console.log('🔧 Configuring Prisma schema with Effect generators...')
91
+ yield* Console.log(`📁 Output path: ${options.output}`)
92
+
93
+ // Check if Prisma is already set up
94
+ yield* checkPrismaSetup()
95
+
96
+ const schemaPath = 'prisma/schema.prisma'
97
+
98
+ yield* Console.log('📄 Schema file already exists.')
99
+
100
+ // Read existing content and check if it has our generators
101
+ const existingContent = yield* readFileContent(schemaPath)
102
+
103
+ if (existingContent.includes('@effectify/prisma prisma generate-effect')) {
104
+ yield* Console.log('✅ Effect generators already configured!')
105
+ return
106
+ }
107
+
108
+ // Add our generators to existing schema
109
+ yield* Console.log('🔧 Adding Effect generators to existing schema...')
110
+
111
+ const updatedContent =
112
+ existingContent +
113
+ `
114
+
115
+ // Effect generators added by @effectify/prisma
116
+ generator effectServices {
117
+ provider = "@effectify/prisma generate-effect"
118
+ output = "../${options.output}/generated/effect-prisma"
119
+ }
120
+
121
+ generator effect {
122
+ provider = "prisma-effect-kysely"
123
+ output = "../${options.output}/generated/effect-prisma"
124
+ }
125
+
126
+ generator sqlSchema {
127
+ provider = "@effectify/prisma generate-sql-schema"
128
+ output = "../${options.output}/generated"
129
+ }
130
+ `
131
+
132
+ yield* writeFileContent(schemaPath, updatedContent)
133
+ yield* Console.log('✅ Effect generators added to existing schema!')
134
+
135
+ yield* Console.log('🎉 Prisma schema initialization completed!')
136
+ yield* Console.log('💡 Next steps:')
137
+ yield* Console.log(' 1. Set your DATABASE_URL environment variable')
138
+ yield* Console.log(' 2. Run: @effectify/prisma prisma generate-effect')
139
+ yield* Console.log(' 3. Run: @effectify/prisma prisma generate-sql-schema')
140
+
141
+ // Ensure the effect completes and exits
142
+ yield* Effect.sync(() => process.exit(0))
143
+ })
144
+
145
+ // Export the init command
146
+ export const initCommand = Command.make(
147
+ 'init',
148
+ {
149
+ output: outputOption,
150
+ },
151
+ ({ output }) =>
152
+ initializePrismaSchema({
153
+ output,
154
+ }).pipe(Effect.provide(NodeFileSystem.layer), Effect.provide(NodePath.layer)),
155
+ )