@effectify/prisma 1.0.1 → 1.1.0
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.
- package/README.md +33 -38
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +15 -14
- package/dist/src/commands/init.d.ts +1 -1
- package/dist/src/commands/init.js +36 -47
- package/dist/src/commands/prisma.d.ts +4 -4
- package/dist/src/commands/prisma.js +12 -12
- package/dist/src/generators/sql-schema-generator.js +15 -16
- package/dist/src/runtime/index.d.ts +303 -0
- package/dist/src/runtime/index.js +216 -0
- package/dist/src/schema-generator/effect/enum.d.ts +12 -0
- package/dist/src/schema-generator/effect/enum.js +18 -0
- package/dist/src/schema-generator/effect/generator.d.ts +16 -0
- package/dist/src/schema-generator/effect/generator.js +42 -0
- package/dist/src/schema-generator/effect/join-table.d.ts +12 -0
- package/dist/src/schema-generator/effect/join-table.js +28 -0
- package/dist/src/schema-generator/effect/type.d.ts +18 -0
- package/dist/src/schema-generator/effect/type.js +82 -0
- package/dist/src/schema-generator/index.d.ts +11 -0
- package/dist/src/schema-generator/index.js +83 -0
- package/dist/src/schema-generator/kysely/generator.d.ts +11 -0
- package/dist/src/schema-generator/kysely/generator.js +7 -0
- package/dist/src/schema-generator/kysely/type.d.ts +14 -0
- package/dist/src/schema-generator/kysely/type.js +44 -0
- package/dist/src/schema-generator/prisma/enum.d.ts +19 -0
- package/dist/src/schema-generator/prisma/enum.js +19 -0
- package/dist/src/schema-generator/prisma/generator.d.ts +53 -0
- package/dist/src/schema-generator/prisma/generator.js +29 -0
- package/dist/src/schema-generator/prisma/relation.d.ts +83 -0
- package/dist/src/schema-generator/prisma/relation.js +165 -0
- package/dist/src/schema-generator/prisma/type.d.ts +108 -0
- package/dist/src/schema-generator/prisma/type.js +85 -0
- package/dist/src/schema-generator/utils/annotations.d.ts +32 -0
- package/dist/src/schema-generator/utils/annotations.js +79 -0
- package/dist/src/schema-generator/utils/codegen.d.ts +9 -0
- package/dist/src/schema-generator/utils/codegen.js +14 -0
- package/dist/src/schema-generator/utils/naming.d.ts +29 -0
- package/dist/src/schema-generator/utils/naming.js +68 -0
- package/dist/src/schema-generator/utils/type-mappings.d.ts +62 -0
- package/dist/src/schema-generator/utils/type-mappings.js +70 -0
- package/dist/src/services/formatter-service.d.ts +10 -0
- package/dist/src/services/formatter-service.js +18 -0
- package/dist/src/services/generator-context.d.ts +2 -2
- package/dist/src/services/generator-context.js +2 -2
- package/dist/src/services/generator-service.d.ts +9 -8
- package/dist/src/services/generator-service.js +39 -43
- package/dist/src/services/render-service.d.ts +3 -3
- package/dist/src/services/render-service.js +8 -8
- package/dist/src/templates/effect-branded-id.eta +2 -0
- package/dist/src/templates/effect-enums.eta +9 -0
- package/dist/src/templates/effect-index.eta +4 -0
- package/dist/src/templates/effect-join-table.eta +8 -0
- package/dist/src/templates/effect-model.eta +6 -0
- package/dist/src/templates/effect-types-header.eta +7 -0
- package/dist/src/templates/index-default.eta +2 -1
- package/dist/src/templates/kysely-db-interface.eta +6 -0
- package/dist/src/templates/prisma-repository.eta +57 -32
- package/package.json +11 -6
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { DMMF } from "@prisma/generator-helper";
|
|
2
|
+
/**
|
|
3
|
+
* Check if a field is a UUID using native DMMF type information
|
|
4
|
+
* 3-tier detection: native type � documentation � field name patterns
|
|
5
|
+
*/
|
|
6
|
+
export declare function isUuidField(field: DMMF.Field): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Get the database column name for a field (respects @map directive)
|
|
9
|
+
*/
|
|
10
|
+
export declare function getFieldDbName(field: DMMF.Field): string;
|
|
11
|
+
/**
|
|
12
|
+
* Check if field has a default value using native DMMF property
|
|
13
|
+
*/
|
|
14
|
+
export declare function hasDefaultValue(field: DMMF.Field): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Check if field is an ID field using native DMMF property
|
|
17
|
+
*/
|
|
18
|
+
export declare function isIdField(field: DMMF.Field): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Check if field is required using native DMMF property
|
|
21
|
+
*/
|
|
22
|
+
export declare function isRequiredField(field: DMMF.Field): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Check if field is a list/array using native DMMF property
|
|
25
|
+
*/
|
|
26
|
+
export declare function isListField(field: DMMF.Field): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Filter models to exclude internal models (starting with _)
|
|
29
|
+
*/
|
|
30
|
+
export declare function filterInternalModels(models: readonly DMMF.Model[]): DMMF.ReadonlyDeep<{
|
|
31
|
+
name: string;
|
|
32
|
+
dbName: string | null;
|
|
33
|
+
schema: string | null;
|
|
34
|
+
fields: DMMF.Field[];
|
|
35
|
+
uniqueFields: string[][];
|
|
36
|
+
uniqueIndexes: DMMF.uniqueIndex[];
|
|
37
|
+
documentation?: string;
|
|
38
|
+
primaryKey: DMMF.PrimaryKey | null;
|
|
39
|
+
isGenerated?: boolean;
|
|
40
|
+
}>[];
|
|
41
|
+
/**
|
|
42
|
+
* Filter fields to only include scalar and enum fields (exclude relations)
|
|
43
|
+
*/
|
|
44
|
+
export declare function filterSchemaFields(fields: readonly DMMF.Field[]): DMMF.ReadonlyDeep<{
|
|
45
|
+
kind: DMMF.FieldKind;
|
|
46
|
+
name: string;
|
|
47
|
+
isRequired: boolean;
|
|
48
|
+
isList: boolean;
|
|
49
|
+
isUnique: boolean;
|
|
50
|
+
isId: boolean;
|
|
51
|
+
isReadOnly: boolean;
|
|
52
|
+
isGenerated?: boolean;
|
|
53
|
+
isUpdatedAt?: boolean;
|
|
54
|
+
type: string;
|
|
55
|
+
nativeType?: [string, string[]] | null;
|
|
56
|
+
dbName?: string | null;
|
|
57
|
+
hasDefaultValue: boolean;
|
|
58
|
+
default?: DMMF.FieldDefault | DMMF.FieldDefaultScalar | DMMF.FieldDefaultScalar[];
|
|
59
|
+
relationFromFields?: string[];
|
|
60
|
+
relationToFields?: string[];
|
|
61
|
+
relationOnDelete?: string;
|
|
62
|
+
relationOnUpdate?: string;
|
|
63
|
+
relationName?: string;
|
|
64
|
+
documentation?: string;
|
|
65
|
+
}>[];
|
|
66
|
+
/**
|
|
67
|
+
* Get the database table name for a model (respects @@map directive)
|
|
68
|
+
*/
|
|
69
|
+
export declare function getModelDbName(model: DMMF.Model): string;
|
|
70
|
+
/**
|
|
71
|
+
* Sort models alphabetically for deterministic output
|
|
72
|
+
*/
|
|
73
|
+
export declare function sortModels(models: readonly DMMF.Model[]): DMMF.ReadonlyDeep<{
|
|
74
|
+
name: string;
|
|
75
|
+
dbName: string | null;
|
|
76
|
+
schema: string | null;
|
|
77
|
+
fields: DMMF.Field[];
|
|
78
|
+
uniqueFields: string[][];
|
|
79
|
+
uniqueIndexes: DMMF.uniqueIndex[];
|
|
80
|
+
documentation?: string;
|
|
81
|
+
primaryKey: DMMF.PrimaryKey | null;
|
|
82
|
+
isGenerated?: boolean;
|
|
83
|
+
}>[];
|
|
84
|
+
/**
|
|
85
|
+
* Sort fields alphabetically for deterministic output
|
|
86
|
+
*/
|
|
87
|
+
export declare function sortFields(fields: readonly DMMF.Field[]): DMMF.ReadonlyDeep<{
|
|
88
|
+
kind: DMMF.FieldKind;
|
|
89
|
+
name: string;
|
|
90
|
+
isRequired: boolean;
|
|
91
|
+
isList: boolean;
|
|
92
|
+
isUnique: boolean;
|
|
93
|
+
isId: boolean;
|
|
94
|
+
isReadOnly: boolean;
|
|
95
|
+
isGenerated?: boolean;
|
|
96
|
+
isUpdatedAt?: boolean;
|
|
97
|
+
type: string;
|
|
98
|
+
nativeType?: [string, string[]] | null;
|
|
99
|
+
dbName?: string | null;
|
|
100
|
+
hasDefaultValue: boolean;
|
|
101
|
+
default?: DMMF.FieldDefault | DMMF.FieldDefaultScalar | DMMF.FieldDefaultScalar[];
|
|
102
|
+
relationFromFields?: string[];
|
|
103
|
+
relationToFields?: string[];
|
|
104
|
+
relationOnDelete?: string;
|
|
105
|
+
relationOnUpdate?: string;
|
|
106
|
+
relationName?: string;
|
|
107
|
+
documentation?: string;
|
|
108
|
+
}>[];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a field is a UUID using native DMMF type information
|
|
3
|
+
* 3-tier detection: native type � documentation � field name patterns
|
|
4
|
+
*/
|
|
5
|
+
export function isUuidField(field) {
|
|
6
|
+
// 1. Check native type (most reliable)
|
|
7
|
+
if (field.nativeType?.[0] === "Uuid") {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
// 2. Check documentation for @db.Uuid
|
|
11
|
+
if (field.documentation?.includes("@db.Uuid")) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
// 3. Fallback: Field name patterns (only for String type)
|
|
15
|
+
if (field.type !== "String") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const uuidFieldPatterns = [
|
|
19
|
+
/^id$/, // Primary ID fields
|
|
20
|
+
/_id$/, // Foreign key ID fields
|
|
21
|
+
/^.*_uuid$/, // uuid suffix
|
|
22
|
+
/^uuid$/, // Direct uuid fields
|
|
23
|
+
];
|
|
24
|
+
return uuidFieldPatterns.some((pattern) => pattern.test(field.name));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get the database column name for a field (respects @map directive)
|
|
28
|
+
*/
|
|
29
|
+
export function getFieldDbName(field) {
|
|
30
|
+
return field.dbName ?? field.name;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if field has a default value using native DMMF property
|
|
34
|
+
*/
|
|
35
|
+
export function hasDefaultValue(field) {
|
|
36
|
+
return field.hasDefaultValue === true;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if field is an ID field using native DMMF property
|
|
40
|
+
*/
|
|
41
|
+
export function isIdField(field) {
|
|
42
|
+
return field.isId === true;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check if field is required using native DMMF property
|
|
46
|
+
*/
|
|
47
|
+
export function isRequiredField(field) {
|
|
48
|
+
return field.isRequired === true;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check if field is a list/array using native DMMF property
|
|
52
|
+
*/
|
|
53
|
+
export function isListField(field) {
|
|
54
|
+
return field.isList === true;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Filter models to exclude internal models (starting with _)
|
|
58
|
+
*/
|
|
59
|
+
export function filterInternalModels(models) {
|
|
60
|
+
return models.filter((model) => !model.name.startsWith("_"));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Filter fields to only include scalar and enum fields (exclude relations)
|
|
64
|
+
*/
|
|
65
|
+
export function filterSchemaFields(fields) {
|
|
66
|
+
return fields.filter((field) => field.kind === "scalar" || field.kind === "enum");
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get the database table name for a model (respects @@map directive)
|
|
70
|
+
*/
|
|
71
|
+
export function getModelDbName(model) {
|
|
72
|
+
return model.dbName ?? model.name;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Sort models alphabetically for deterministic output
|
|
76
|
+
*/
|
|
77
|
+
export function sortModels(models) {
|
|
78
|
+
return models.slice().sort((a, b) => a.name.localeCompare(b.name));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Sort fields alphabetically for deterministic output
|
|
82
|
+
*/
|
|
83
|
+
export function sortFields(fields) {
|
|
84
|
+
return fields.slice().sort((a, b) => a.name.localeCompare(b.name));
|
|
85
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DMMF } from "@prisma/generator-helper";
|
|
2
|
+
/**
|
|
3
|
+
* @customType Annotation Parser
|
|
4
|
+
*
|
|
5
|
+
* Allows overriding Effect Schema types for Prisma-supported fields.
|
|
6
|
+
*
|
|
7
|
+
* WORKS FOR: Prisma scalar types (String, Int, Boolean, DateTime, etc.)
|
|
8
|
+
*
|
|
9
|
+
* USE CASES:
|
|
10
|
+
* // Email validation for String field
|
|
11
|
+
* /// @customType(Schema.String.pipe(Schema.email()))
|
|
12
|
+
* email String
|
|
13
|
+
*
|
|
14
|
+
* // Positive number constraint for Int field
|
|
15
|
+
* /// @customType(Schema.Number.pipe(Schema.positive()))
|
|
16
|
+
* age Int
|
|
17
|
+
*
|
|
18
|
+
* // Custom branded type
|
|
19
|
+
* /// @customType(Schema.String.pipe(Schema.brand('UserId')))
|
|
20
|
+
* userId String
|
|
21
|
+
*
|
|
22
|
+
* @param field - Prisma DMMF field
|
|
23
|
+
* @returns Extracted type string or null if no annotation found
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractEffectTypeOverride(field: DMMF.Field): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Check if field has any custom type annotations
|
|
28
|
+
*
|
|
29
|
+
* @param fields - Array of Prisma fields
|
|
30
|
+
* @returns true if any field uses custom types in @effectType
|
|
31
|
+
*/
|
|
32
|
+
export declare function hasCustomTypeAnnotations(fields: readonly DMMF.Field[]): boolean;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @customType Annotation Parser
|
|
3
|
+
*
|
|
4
|
+
* Allows overriding Effect Schema types for Prisma-supported fields.
|
|
5
|
+
*
|
|
6
|
+
* WORKS FOR: Prisma scalar types (String, Int, Boolean, DateTime, etc.)
|
|
7
|
+
*
|
|
8
|
+
* USE CASES:
|
|
9
|
+
* // Email validation for String field
|
|
10
|
+
* /// @customType(Schema.String.pipe(Schema.email()))
|
|
11
|
+
* email String
|
|
12
|
+
*
|
|
13
|
+
* // Positive number constraint for Int field
|
|
14
|
+
* /// @customType(Schema.Number.pipe(Schema.positive()))
|
|
15
|
+
* age Int
|
|
16
|
+
*
|
|
17
|
+
* // Custom branded type
|
|
18
|
+
* /// @customType(Schema.String.pipe(Schema.brand('UserId')))
|
|
19
|
+
* userId String
|
|
20
|
+
*
|
|
21
|
+
* @param field - Prisma DMMF field
|
|
22
|
+
* @returns Extracted type string or null if no annotation found
|
|
23
|
+
*/
|
|
24
|
+
export function extractEffectTypeOverride(field) {
|
|
25
|
+
if (!field.documentation)
|
|
26
|
+
return null;
|
|
27
|
+
// Match @customType annotation - handle balanced parentheses
|
|
28
|
+
const annotationMatch = field.documentation.match(/@customType\s*\(/);
|
|
29
|
+
if (!annotationMatch)
|
|
30
|
+
return null;
|
|
31
|
+
// Find the matching closing parenthesis
|
|
32
|
+
const startIdx = field.documentation.indexOf("@customType(") + "@customType(".length;
|
|
33
|
+
let parenCount = 1;
|
|
34
|
+
let endIdx = startIdx;
|
|
35
|
+
for (let i = startIdx; i < field.documentation.length && parenCount > 0; i++) {
|
|
36
|
+
if (field.documentation[i] === "(")
|
|
37
|
+
parenCount++;
|
|
38
|
+
if (field.documentation[i] === ")")
|
|
39
|
+
parenCount--;
|
|
40
|
+
if (parenCount === 0) {
|
|
41
|
+
endIdx = i;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (parenCount !== 0) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const typeStr = field.documentation.substring(startIdx, endIdx).trim();
|
|
49
|
+
// Validate it's either a custom type or starts with Schema.
|
|
50
|
+
if (!(typeStr.startsWith("Schema.") || isCustomType(typeStr))) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return typeStr;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if type string is a custom type reference
|
|
57
|
+
*
|
|
58
|
+
* Custom types are PascalCase identifiers without dots:
|
|
59
|
+
* - Valid: Vector1536, JSONBType, CustomEnum
|
|
60
|
+
* - Invalid: Schema.String, some.nested.type
|
|
61
|
+
*
|
|
62
|
+
* @param typeStr - Type string to check
|
|
63
|
+
* @returns true if it's a custom type reference
|
|
64
|
+
*/
|
|
65
|
+
function isCustomType(typeStr) {
|
|
66
|
+
return /^[A-Z][A-Za-z0-9]*$/.test(typeStr);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if field has any custom type annotations
|
|
70
|
+
*
|
|
71
|
+
* @param fields - Array of Prisma fields
|
|
72
|
+
* @returns true if any field uses custom types in @effectType
|
|
73
|
+
*/
|
|
74
|
+
export function hasCustomTypeAnnotations(fields) {
|
|
75
|
+
return fields.some((field) => {
|
|
76
|
+
const override = extractEffectTypeOverride(field);
|
|
77
|
+
return override && isCustomType(override);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code generation utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generate standard file header for generated files
|
|
6
|
+
*
|
|
7
|
+
* @param timestamp - Optional timestamp (defaults to current time)
|
|
8
|
+
*/
|
|
9
|
+
export function generateFileHeader(timestamp = new Date()) {
|
|
10
|
+
return `/**
|
|
11
|
+
* Generated: ${timestamp.toISOString()}
|
|
12
|
+
* DO NOT EDIT MANUALLY
|
|
13
|
+
*/`;
|
|
14
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Naming utilities for consistent TypeScript identifier generation
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Convert any string to PascalCase with optional suffix
|
|
6
|
+
* Handles: snake_case, camelCase, kebab-case, or mixed formats
|
|
7
|
+
*
|
|
8
|
+
* @param str - The string to convert
|
|
9
|
+
* @param suffix - Optional suffix to append (e.g., "Schema", "Type")
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* toPascalCase('user') // 'User'
|
|
13
|
+
* toPascalCase('PRODUCT_STATUS') // 'ProductStatus'
|
|
14
|
+
* toPascalCase('PRODUCT_STATUS', 'Schema') // 'ProductStatusSchema'
|
|
15
|
+
* toPascalCase('USER_ROLE', 'Type') // 'UserRoleType'
|
|
16
|
+
*/
|
|
17
|
+
export declare function toPascalCase(str: string, suffix?: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Convert PascalCase or camelCase string to snake_case
|
|
20
|
+
* Used for generating database column names from model names
|
|
21
|
+
*
|
|
22
|
+
* @param str - The string to convert
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* toSnakeCase('User') // 'user'
|
|
26
|
+
* toSnakeCase('ProductTag') // 'product_tag'
|
|
27
|
+
* toSnakeCase('ProductStatus') // 'product_status'
|
|
28
|
+
*/
|
|
29
|
+
export declare function toSnakeCase(str: string): string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Naming utilities for consistent TypeScript identifier generation
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Convert any string to PascalCase with optional suffix
|
|
6
|
+
* Handles: snake_case, camelCase, kebab-case, or mixed formats
|
|
7
|
+
*
|
|
8
|
+
* @param str - The string to convert
|
|
9
|
+
* @param suffix - Optional suffix to append (e.g., "Schema", "Type")
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* toPascalCase('user') // 'User'
|
|
13
|
+
* toPascalCase('PRODUCT_STATUS') // 'ProductStatus'
|
|
14
|
+
* toPascalCase('PRODUCT_STATUS', 'Schema') // 'ProductStatusSchema'
|
|
15
|
+
* toPascalCase('USER_ROLE', 'Type') // 'UserRoleType'
|
|
16
|
+
*/
|
|
17
|
+
export function toPascalCase(str, suffix) {
|
|
18
|
+
// Handle empty string
|
|
19
|
+
if (!str)
|
|
20
|
+
return str;
|
|
21
|
+
// Split on underscores, dashes, or spaces
|
|
22
|
+
const words = str.split(/[_-\s]+/);
|
|
23
|
+
let pascalCase;
|
|
24
|
+
// If no delimiters found, check if already camelCase/PascalCase
|
|
25
|
+
if (words.length === 1) {
|
|
26
|
+
// Split camelCase/PascalCase by capital letters
|
|
27
|
+
const camelWords = str.split(/(?=[A-Z])/);
|
|
28
|
+
if (camelWords.length > 1) {
|
|
29
|
+
pascalCase = camelWords
|
|
30
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
31
|
+
.join("");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// Single word - just capitalize first letter
|
|
35
|
+
pascalCase = str.charAt(0).toUpperCase() + str.slice(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Convert each word to PascalCase
|
|
40
|
+
pascalCase = words
|
|
41
|
+
.filter((word) => word.length > 0) // Remove empty strings
|
|
42
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
43
|
+
.join("");
|
|
44
|
+
}
|
|
45
|
+
return suffix ? `${pascalCase}${suffix}` : pascalCase;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Convert PascalCase or camelCase string to snake_case
|
|
49
|
+
* Used for generating database column names from model names
|
|
50
|
+
*
|
|
51
|
+
* @param str - The string to convert
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* toSnakeCase('User') // 'user'
|
|
55
|
+
* toSnakeCase('ProductTag') // 'product_tag'
|
|
56
|
+
* toSnakeCase('ProductStatus') // 'product_status'
|
|
57
|
+
*/
|
|
58
|
+
export function toSnakeCase(str) {
|
|
59
|
+
if (!str)
|
|
60
|
+
return str;
|
|
61
|
+
return (str
|
|
62
|
+
// Insert underscore before uppercase letters (except at start)
|
|
63
|
+
.replace(/([A-Z])/g, "_$1")
|
|
64
|
+
// Remove leading underscore if present
|
|
65
|
+
.replace(/^_/, "")
|
|
66
|
+
// Convert to lowercase
|
|
67
|
+
.toLowerCase());
|
|
68
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Prisma type mappings
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for mapping Prisma scalar types to:
|
|
5
|
+
* - Effect Schema types (for schema generation)
|
|
6
|
+
* - TypeScript types (for Kysely interfaces)
|
|
7
|
+
*
|
|
8
|
+
* This eliminates duplication across effect/type.ts, kysely/type.ts, and effect/join-table.ts
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Prisma scalar type mapping to Effect Schema types
|
|
12
|
+
* Uses const assertion for type safety
|
|
13
|
+
*
|
|
14
|
+
* Note: DateTime uses Schema.DateFromSelf (not Schema.Date) so that:
|
|
15
|
+
* - Type = Date (runtime)
|
|
16
|
+
* - Encoded = Date (database)
|
|
17
|
+
* This allows Kysely to work with native Date objects directly.
|
|
18
|
+
* Schema.Date would encode to string, requiring ISO string conversions.
|
|
19
|
+
*/
|
|
20
|
+
export declare const PRISMA_TO_EFFECT_SCHEMA: {
|
|
21
|
+
readonly String: "Schema.String";
|
|
22
|
+
readonly Int: "Schema.Number";
|
|
23
|
+
readonly Float: "Schema.Number";
|
|
24
|
+
readonly BigInt: "Schema.BigInt";
|
|
25
|
+
readonly Decimal: "Schema.String";
|
|
26
|
+
readonly Boolean: "Schema.Boolean";
|
|
27
|
+
readonly DateTime: "Schema.DateFromSelf";
|
|
28
|
+
readonly Json: "Schema.Unknown";
|
|
29
|
+
readonly Bytes: "Schema.Uint8Array";
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Prisma scalar type mapping to TypeScript types (for Kysely interfaces)
|
|
33
|
+
*/
|
|
34
|
+
export declare const PRISMA_TO_TYPESCRIPT: {
|
|
35
|
+
readonly String: "string";
|
|
36
|
+
readonly Int: "number";
|
|
37
|
+
readonly Float: "number";
|
|
38
|
+
readonly Boolean: "boolean";
|
|
39
|
+
readonly DateTime: "Date";
|
|
40
|
+
readonly Json: "unknown";
|
|
41
|
+
readonly Bytes: "Buffer";
|
|
42
|
+
readonly Decimal: "string";
|
|
43
|
+
readonly BigInt: "string";
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Type-safe key type for Prisma scalar types
|
|
47
|
+
*/
|
|
48
|
+
export type PrismaScalarType = keyof typeof PRISMA_TO_EFFECT_SCHEMA;
|
|
49
|
+
/**
|
|
50
|
+
* Type guard to check if a string is a valid Prisma scalar type
|
|
51
|
+
*/
|
|
52
|
+
export declare function isPrismaScalarType(type: string): type is PrismaScalarType;
|
|
53
|
+
/**
|
|
54
|
+
* Get Effect Schema type for a Prisma scalar type
|
|
55
|
+
* Returns undefined for non-scalar types (enums, relations)
|
|
56
|
+
*/
|
|
57
|
+
export declare function getEffectSchemaType(type: string): "Schema.String" | "Schema.Number" | "Schema.BigInt" | "Schema.Boolean" | "Schema.DateFromSelf" | "Schema.Unknown" | "Schema.Uint8Array" | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Get TypeScript type for a Prisma scalar type
|
|
60
|
+
* Returns the input type unchanged for non-scalar types (enums, models)
|
|
61
|
+
*/
|
|
62
|
+
export declare function getTypeScriptType(type: string): string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Prisma type mappings
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for mapping Prisma scalar types to:
|
|
5
|
+
* - Effect Schema types (for schema generation)
|
|
6
|
+
* - TypeScript types (for Kysely interfaces)
|
|
7
|
+
*
|
|
8
|
+
* This eliminates duplication across effect/type.ts, kysely/type.ts, and effect/join-table.ts
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Prisma scalar type mapping to Effect Schema types
|
|
12
|
+
* Uses const assertion for type safety
|
|
13
|
+
*
|
|
14
|
+
* Note: DateTime uses Schema.DateFromSelf (not Schema.Date) so that:
|
|
15
|
+
* - Type = Date (runtime)
|
|
16
|
+
* - Encoded = Date (database)
|
|
17
|
+
* This allows Kysely to work with native Date objects directly.
|
|
18
|
+
* Schema.Date would encode to string, requiring ISO string conversions.
|
|
19
|
+
*/
|
|
20
|
+
export const PRISMA_TO_EFFECT_SCHEMA = {
|
|
21
|
+
String: "Schema.String",
|
|
22
|
+
Int: "Schema.Number",
|
|
23
|
+
Float: "Schema.Number",
|
|
24
|
+
BigInt: "Schema.BigInt",
|
|
25
|
+
Decimal: "Schema.String", // For precision
|
|
26
|
+
Boolean: "Schema.Boolean",
|
|
27
|
+
DateTime: "Schema.DateFromSelf", // Native Date type for Kysely compatibility
|
|
28
|
+
Json: "Schema.Unknown", // Safe unknown type
|
|
29
|
+
Bytes: "Schema.Uint8Array",
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Prisma scalar type mapping to TypeScript types (for Kysely interfaces)
|
|
33
|
+
*/
|
|
34
|
+
export const PRISMA_TO_TYPESCRIPT = {
|
|
35
|
+
String: "string",
|
|
36
|
+
Int: "number",
|
|
37
|
+
Float: "number",
|
|
38
|
+
Boolean: "boolean",
|
|
39
|
+
DateTime: "Date",
|
|
40
|
+
Json: "unknown",
|
|
41
|
+
Bytes: "Buffer",
|
|
42
|
+
Decimal: "string",
|
|
43
|
+
BigInt: "string", // Kysely convention
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Type guard to check if a string is a valid Prisma scalar type
|
|
47
|
+
*/
|
|
48
|
+
export function isPrismaScalarType(type) {
|
|
49
|
+
return type in PRISMA_TO_EFFECT_SCHEMA;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get Effect Schema type for a Prisma scalar type
|
|
53
|
+
* Returns undefined for non-scalar types (enums, relations)
|
|
54
|
+
*/
|
|
55
|
+
export function getEffectSchemaType(type) {
|
|
56
|
+
if (isPrismaScalarType(type)) {
|
|
57
|
+
return PRISMA_TO_EFFECT_SCHEMA[type];
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get TypeScript type for a Prisma scalar type
|
|
63
|
+
* Returns the input type unchanged for non-scalar types (enums, models)
|
|
64
|
+
*/
|
|
65
|
+
export function getTypeScriptType(type) {
|
|
66
|
+
if (isPrismaScalarType(type)) {
|
|
67
|
+
return PRISMA_TO_TYPESCRIPT[type];
|
|
68
|
+
}
|
|
69
|
+
return type;
|
|
70
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
declare const FormatterService_base: Context.TagClass<FormatterService, "FormatterService", {
|
|
5
|
+
readonly format: (code: string) => Effect.Effect<string, Error>;
|
|
6
|
+
}>;
|
|
7
|
+
export declare class FormatterService extends FormatterService_base {
|
|
8
|
+
static Live: Layer.Layer<FormatterService, never, never>;
|
|
9
|
+
}
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import { createFromBuffer } from "@dprint/formatter";
|
|
5
|
+
import { getPath } from "@dprint/typescript";
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
export class FormatterService extends Context.Tag("FormatterService")() {
|
|
8
|
+
static Live = Layer.sync(FormatterService, () => {
|
|
9
|
+
const buffer = fs.readFileSync(getPath());
|
|
10
|
+
const formatter = createFromBuffer(buffer);
|
|
11
|
+
return {
|
|
12
|
+
format: (code) => Effect.try({
|
|
13
|
+
try: () => formatter.formatText({ filePath: "file.ts", fileText: code }),
|
|
14
|
+
catch: (error) => new Error(`Failed to format code: ${error}`),
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { GeneratorOptions } from
|
|
2
|
-
import * as Context from
|
|
1
|
+
import type { GeneratorOptions } from "@prisma/generator-helper";
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
3
|
declare const GeneratorContext_base: Context.TagClass<GeneratorContext, "GeneratorContext", GeneratorOptions>;
|
|
4
4
|
export declare class GeneratorContext extends GeneratorContext_base {
|
|
5
5
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import * as Context from
|
|
2
|
-
export class GeneratorContext extends Context.Tag(
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
export class GeneratorContext extends Context.Tag("GeneratorContext")() {
|
|
3
3
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import * as FileSystem from
|
|
2
|
-
import * as Path from
|
|
3
|
-
import * as Context from
|
|
4
|
-
import * as Effect from
|
|
5
|
-
import * as Layer from
|
|
6
|
-
import { GeneratorContext } from
|
|
7
|
-
import { RenderService } from
|
|
1
|
+
import * as FileSystem from "@effect/platform/FileSystem";
|
|
2
|
+
import * as Path from "@effect/platform/Path";
|
|
3
|
+
import * as Context from "effect/Context";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Layer from "effect/Layer";
|
|
6
|
+
import { GeneratorContext } from "./generator-context.js";
|
|
7
|
+
import { RenderService } from "./render-service.js";
|
|
8
|
+
import { FormatterService } from "./formatter-service.js";
|
|
8
9
|
declare const GeneratorService_base: Context.TagClass<GeneratorService, "GeneratorService", {
|
|
9
10
|
readonly generate: Effect.Effect<void, Error, GeneratorContext>;
|
|
10
11
|
}>;
|
|
11
12
|
export declare class GeneratorService extends GeneratorService_base {
|
|
12
|
-
static Live: Layer.Layer<GeneratorService, never, FileSystem.FileSystem | Path.Path | RenderService>;
|
|
13
|
+
static Live: Layer.Layer<GeneratorService, never, FileSystem.FileSystem | Path.Path | RenderService | FormatterService>;
|
|
13
14
|
}
|
|
14
15
|
export {};
|