@odata-effect/odata-effect-generator 0.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/Cli/package.json +6 -0
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/bin/package.json +6 -0
- package/digester/Digester/package.json +6 -0
- package/digester/TypeMapper/package.json +6 -0
- package/dist/cjs/Cli.js +95 -0
- package/dist/cjs/Cli.js.map +1 -0
- package/dist/cjs/bin.js +14 -0
- package/dist/cjs/bin.js.map +1 -0
- package/dist/cjs/digester/Digester.js +488 -0
- package/dist/cjs/digester/Digester.js.map +1 -0
- package/dist/cjs/digester/TypeMapper.js +262 -0
- package/dist/cjs/digester/TypeMapper.js.map +1 -0
- package/dist/cjs/generator/Generator.js +126 -0
- package/dist/cjs/generator/Generator.js.map +1 -0
- package/dist/cjs/generator/IndexGenerator.js +164 -0
- package/dist/cjs/generator/IndexGenerator.js.map +1 -0
- package/dist/cjs/generator/ModelsGenerator.js +255 -0
- package/dist/cjs/generator/ModelsGenerator.js.map +1 -0
- package/dist/cjs/generator/NamingHelper.js +164 -0
- package/dist/cjs/generator/NamingHelper.js.map +1 -0
- package/dist/cjs/generator/PackageGenerator.js +168 -0
- package/dist/cjs/generator/PackageGenerator.js.map +1 -0
- package/dist/cjs/generator/QueryModelsGenerator.js +182 -0
- package/dist/cjs/generator/QueryModelsGenerator.js.map +1 -0
- package/dist/cjs/generator/ServiceFnGenerator.js +258 -0
- package/dist/cjs/generator/ServiceFnGenerator.js.map +1 -0
- package/dist/cjs/generator/ServiceFnPromiseGenerator.js +182 -0
- package/dist/cjs/generator/ServiceFnPromiseGenerator.js.map +1 -0
- package/dist/cjs/index.js +38 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/model/DataModel.js +28 -0
- package/dist/cjs/model/DataModel.js.map +1 -0
- package/dist/cjs/parser/EdmxSchema.js +20 -0
- package/dist/cjs/parser/EdmxSchema.js.map +1 -0
- package/dist/cjs/parser/XmlParser.js +47 -0
- package/dist/cjs/parser/XmlParser.js.map +1 -0
- package/dist/dts/Cli.d.ts +9 -0
- package/dist/dts/Cli.d.ts.map +1 -0
- package/dist/dts/bin.d.ts +3 -0
- package/dist/dts/bin.d.ts.map +1 -0
- package/dist/dts/digester/Digester.d.ts +33 -0
- package/dist/dts/digester/Digester.d.ts.map +1 -0
- package/dist/dts/digester/TypeMapper.d.ts +79 -0
- package/dist/dts/digester/TypeMapper.d.ts.map +1 -0
- package/dist/dts/generator/Generator.d.ts +45 -0
- package/dist/dts/generator/Generator.d.ts.map +1 -0
- package/dist/dts/generator/IndexGenerator.d.ts +14 -0
- package/dist/dts/generator/IndexGenerator.d.ts.map +1 -0
- package/dist/dts/generator/ModelsGenerator.d.ts +14 -0
- package/dist/dts/generator/ModelsGenerator.d.ts.map +1 -0
- package/dist/dts/generator/NamingHelper.d.ts +110 -0
- package/dist/dts/generator/NamingHelper.d.ts.map +1 -0
- package/dist/dts/generator/PackageGenerator.d.ts +53 -0
- package/dist/dts/generator/PackageGenerator.d.ts.map +1 -0
- package/dist/dts/generator/QueryModelsGenerator.d.ts +14 -0
- package/dist/dts/generator/QueryModelsGenerator.d.ts.map +1 -0
- package/dist/dts/generator/ServiceFnGenerator.d.ts +37 -0
- package/dist/dts/generator/ServiceFnGenerator.d.ts.map +1 -0
- package/dist/dts/generator/ServiceFnPromiseGenerator.d.ts +40 -0
- package/dist/dts/generator/ServiceFnPromiseGenerator.d.ts.map +1 -0
- package/dist/dts/index.d.ts +101 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/model/DataModel.d.ts +188 -0
- package/dist/dts/model/DataModel.d.ts.map +1 -0
- package/dist/dts/parser/EdmxSchema.d.ts +228 -0
- package/dist/dts/parser/EdmxSchema.d.ts.map +1 -0
- package/dist/dts/parser/XmlParser.d.ts +31 -0
- package/dist/dts/parser/XmlParser.d.ts.map +1 -0
- package/dist/esm/Cli.js +87 -0
- package/dist/esm/Cli.js.map +1 -0
- package/dist/esm/bin.js +12 -0
- package/dist/esm/bin.js.map +1 -0
- package/dist/esm/digester/Digester.js +478 -0
- package/dist/esm/digester/Digester.js.map +1 -0
- package/dist/esm/digester/TypeMapper.js +248 -0
- package/dist/esm/digester/TypeMapper.js.map +1 -0
- package/dist/esm/generator/Generator.js +116 -0
- package/dist/esm/generator/Generator.js.map +1 -0
- package/dist/esm/generator/IndexGenerator.js +157 -0
- package/dist/esm/generator/IndexGenerator.js.map +1 -0
- package/dist/esm/generator/ModelsGenerator.js +248 -0
- package/dist/esm/generator/ModelsGenerator.js.map +1 -0
- package/dist/esm/generator/NamingHelper.js +147 -0
- package/dist/esm/generator/NamingHelper.js.map +1 -0
- package/dist/esm/generator/PackageGenerator.js +156 -0
- package/dist/esm/generator/PackageGenerator.js.map +1 -0
- package/dist/esm/generator/QueryModelsGenerator.js +175 -0
- package/dist/esm/generator/QueryModelsGenerator.js.map +1 -0
- package/dist/esm/generator/ServiceFnGenerator.js +251 -0
- package/dist/esm/generator/ServiceFnGenerator.js.map +1 -0
- package/dist/esm/generator/ServiceFnPromiseGenerator.js +174 -0
- package/dist/esm/generator/ServiceFnPromiseGenerator.js.map +1 -0
- package/dist/esm/index.js +101 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/model/DataModel.js +21 -0
- package/dist/esm/model/DataModel.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/dist/esm/parser/EdmxSchema.js +13 -0
- package/dist/esm/parser/EdmxSchema.js.map +1 -0
- package/dist/esm/parser/XmlParser.js +37 -0
- package/dist/esm/parser/XmlParser.js.map +1 -0
- package/generator/Generator/package.json +6 -0
- package/generator/IndexGenerator/package.json +6 -0
- package/generator/ModelsGenerator/package.json +6 -0
- package/generator/NamingHelper/package.json +6 -0
- package/generator/PackageGenerator/package.json +6 -0
- package/generator/QueryModelsGenerator/package.json +6 -0
- package/generator/ServiceFnGenerator/package.json +6 -0
- package/generator/ServiceFnPromiseGenerator/package.json +6 -0
- package/model/DataModel/package.json +6 -0
- package/package.json +157 -0
- package/parser/EdmxSchema/package.json +6 -0
- package/parser/XmlParser/package.json +6 -0
- package/src/Cli.ts +115 -0
- package/src/bin.ts +17 -0
- package/src/digester/Digester.ts +600 -0
- package/src/digester/TypeMapper.ts +181 -0
- package/src/generator/Generator.ts +183 -0
- package/src/generator/IndexGenerator.ts +189 -0
- package/src/generator/ModelsGenerator.ts +344 -0
- package/src/generator/NamingHelper.ts +159 -0
- package/src/generator/PackageGenerator.ts +176 -0
- package/src/generator/QueryModelsGenerator.ts +228 -0
- package/src/generator/ServiceFnGenerator.ts +324 -0
- package/src/generator/ServiceFnPromiseGenerator.ts +242 -0
- package/src/index.ts +114 -0
- package/src/model/DataModel.ts +254 -0
- package/src/parser/EdmxSchema.ts +308 -0
- package/src/parser/XmlParser.ts +47 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type mapper for converting OData types to Effect Schema types and query paths.
|
|
3
|
+
*
|
|
4
|
+
* @since 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
import type { TypeMapping } from "../model/DataModel.js"
|
|
7
|
+
import type { ODataVersion } from "../parser/EdmxSchema.js"
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Type Mappings
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default type mapping for unknown types.
|
|
15
|
+
*/
|
|
16
|
+
const UNKNOWN_TYPE: TypeMapping = {
|
|
17
|
+
effectSchema: "Schema.Unknown",
|
|
18
|
+
queryPath: "StringPath",
|
|
19
|
+
tsType: "unknown"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* OData V2 type mappings.
|
|
24
|
+
*/
|
|
25
|
+
const V2_TYPE_MAP: Record<string, TypeMapping> = {
|
|
26
|
+
"Edm.String": { effectSchema: "Schema.String", queryPath: "StringPath", tsType: "string" },
|
|
27
|
+
"Edm.Boolean": { effectSchema: "Schema.Boolean", queryPath: "BooleanPath", tsType: "boolean" },
|
|
28
|
+
"Edm.Byte": { effectSchema: "Schema.Number", queryPath: "NumberPath", tsType: "number" },
|
|
29
|
+
"Edm.SByte": { effectSchema: "Schema.Number", queryPath: "NumberPath", tsType: "number" },
|
|
30
|
+
"Edm.Int16": { effectSchema: "Schema.Number", queryPath: "NumberPath", tsType: "number" },
|
|
31
|
+
"Edm.Int32": { effectSchema: "Schema.Number", queryPath: "NumberPath", tsType: "number" },
|
|
32
|
+
"Edm.Int64": { effectSchema: "Schema.String", queryPath: "StringPath", tsType: "string" },
|
|
33
|
+
"Edm.Single": { effectSchema: "Schema.Number", queryPath: "NumberPath", tsType: "number" },
|
|
34
|
+
"Edm.Double": { effectSchema: "Schema.Number", queryPath: "NumberPath", tsType: "number" },
|
|
35
|
+
"Edm.Decimal": { effectSchema: "Schema.String", queryPath: "StringPath", tsType: "string" },
|
|
36
|
+
"Edm.Guid": { effectSchema: "Schema.String", queryPath: "StringPath", tsType: "string" },
|
|
37
|
+
"Edm.Time": { effectSchema: "Schema.String", queryPath: "StringPath", tsType: "string" },
|
|
38
|
+
"Edm.DateTime": { effectSchema: "Schema.Date", queryPath: "DateTimePath", tsType: "Date" },
|
|
39
|
+
"Edm.DateTimeOffset": { effectSchema: "Schema.Date", queryPath: "DateTimePath", tsType: "Date" },
|
|
40
|
+
"Edm.Binary": { effectSchema: "Schema.String", queryPath: "StringPath", tsType: "string" }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* OData V4 type mappings (extends V2).
|
|
45
|
+
*/
|
|
46
|
+
const V4_TYPE_MAP: Record<string, TypeMapping> = {
|
|
47
|
+
...V2_TYPE_MAP,
|
|
48
|
+
"Edm.Date": { effectSchema: "Schema.String", queryPath: "StringPath", tsType: "string" },
|
|
49
|
+
"Edm.TimeOfDay": { effectSchema: "Schema.String", queryPath: "StringPath", tsType: "string" },
|
|
50
|
+
"Edm.Duration": { effectSchema: "Schema.String", queryPath: "StringPath", tsType: "string" },
|
|
51
|
+
"Edm.Stream": { effectSchema: "Schema.String", queryPath: "StringPath", tsType: "string" },
|
|
52
|
+
"Edm.GeographyPoint": { effectSchema: "Schema.Unknown", queryPath: "StringPath", tsType: "unknown" },
|
|
53
|
+
"Edm.GeographyLineString": { effectSchema: "Schema.Unknown", queryPath: "StringPath", tsType: "unknown" },
|
|
54
|
+
"Edm.GeographyPolygon": { effectSchema: "Schema.Unknown", queryPath: "StringPath", tsType: "unknown" },
|
|
55
|
+
"Edm.GeometryPoint": { effectSchema: "Schema.Unknown", queryPath: "StringPath", tsType: "unknown" },
|
|
56
|
+
"Edm.GeometryLineString": { effectSchema: "Schema.Unknown", queryPath: "StringPath", tsType: "unknown" },
|
|
57
|
+
"Edm.GeometryPolygon": { effectSchema: "Schema.Unknown", queryPath: "StringPath", tsType: "unknown" }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Type Mapper
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Parsed OData type information.
|
|
66
|
+
*/
|
|
67
|
+
export interface ParsedType {
|
|
68
|
+
readonly baseType: string
|
|
69
|
+
readonly isCollection: boolean
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parse an OData type string to extract base type and collection info.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* parseODataType("Edm.String") // { baseType: "Edm.String", isCollection: false }
|
|
77
|
+
* parseODataType("Collection(Edm.String)") // { baseType: "Edm.String", isCollection: true }
|
|
78
|
+
*
|
|
79
|
+
* @since 1.0.0
|
|
80
|
+
* @category parsing
|
|
81
|
+
*/
|
|
82
|
+
export const parseODataType = (typeString: string): ParsedType => {
|
|
83
|
+
const collectionMatch = typeString.match(/^Collection\((.+)\)$/)
|
|
84
|
+
if (collectionMatch) {
|
|
85
|
+
return {
|
|
86
|
+
baseType: collectionMatch[1],
|
|
87
|
+
isCollection: true
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
baseType: typeString,
|
|
92
|
+
isCollection: false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Check if a type is a primitive EDM type.
|
|
98
|
+
*
|
|
99
|
+
* @since 1.0.0
|
|
100
|
+
* @category type-checking
|
|
101
|
+
*/
|
|
102
|
+
export const isPrimitiveType = (type: string): boolean => {
|
|
103
|
+
return type.startsWith("Edm.")
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the type mapping for a primitive OData type.
|
|
108
|
+
*
|
|
109
|
+
* @since 1.0.0
|
|
110
|
+
* @category type-mapping
|
|
111
|
+
*/
|
|
112
|
+
export const getPrimitiveTypeMapping = (
|
|
113
|
+
version: ODataVersion,
|
|
114
|
+
type: string
|
|
115
|
+
): TypeMapping => {
|
|
116
|
+
const typeMap = version === "V2" ? V2_TYPE_MAP : V4_TYPE_MAP
|
|
117
|
+
return typeMap[type] ?? UNKNOWN_TYPE
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create a type mapping for a complex type reference.
|
|
122
|
+
*
|
|
123
|
+
* @since 1.0.0
|
|
124
|
+
* @category type-mapping
|
|
125
|
+
*/
|
|
126
|
+
export const getComplexTypeMapping = (typeName: string): TypeMapping => ({
|
|
127
|
+
effectSchema: typeName,
|
|
128
|
+
queryPath: `EntityPath<Q${typeName}>`,
|
|
129
|
+
tsType: typeName
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create a type mapping for an enum type reference.
|
|
134
|
+
*
|
|
135
|
+
* @since 1.0.0
|
|
136
|
+
* @category type-mapping
|
|
137
|
+
*/
|
|
138
|
+
export const getEnumTypeMapping = (typeName: string): TypeMapping => ({
|
|
139
|
+
effectSchema: typeName,
|
|
140
|
+
queryPath: "StringPath",
|
|
141
|
+
tsType: typeName
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Create a type mapping for a navigation property (single entity).
|
|
146
|
+
*
|
|
147
|
+
* @since 1.0.0
|
|
148
|
+
* @category type-mapping
|
|
149
|
+
*/
|
|
150
|
+
export const getNavigationTypeMapping = (typeName: string): TypeMapping => ({
|
|
151
|
+
effectSchema: `Schema.optional(${typeName})`,
|
|
152
|
+
queryPath: `EntityPath<Q${typeName}>`,
|
|
153
|
+
tsType: `${typeName} | undefined`
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create a type mapping for a navigation property (collection).
|
|
158
|
+
*
|
|
159
|
+
* @since 1.0.0
|
|
160
|
+
* @category type-mapping
|
|
161
|
+
*/
|
|
162
|
+
export const getCollectionNavigationTypeMapping = (typeName: string): TypeMapping => ({
|
|
163
|
+
effectSchema: `Schema.optional(Schema.Array(${typeName}))`,
|
|
164
|
+
queryPath: `CollectionPath<Q${typeName}>`,
|
|
165
|
+
tsType: `ReadonlyArray<${typeName}> | undefined`
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Extract the simple type name from a fully qualified name.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* getSimpleTypeName("ODataDemo.Product") // "Product"
|
|
173
|
+
* getSimpleTypeName("Product") // "Product"
|
|
174
|
+
*
|
|
175
|
+
* @since 1.0.0
|
|
176
|
+
* @category naming
|
|
177
|
+
*/
|
|
178
|
+
export const getSimpleTypeName = (fqName: string): string => {
|
|
179
|
+
const parts = fqName.split(".")
|
|
180
|
+
return parts[parts.length - 1]
|
|
181
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main generator that orchestrates all code generation.
|
|
3
|
+
*
|
|
4
|
+
* @since 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
import * as FileSystem from "@effect/platform/FileSystem"
|
|
7
|
+
import * as Path from "@effect/platform/Path"
|
|
8
|
+
import * as Effect from "effect/Effect"
|
|
9
|
+
import * as Schema from "effect/Schema"
|
|
10
|
+
import type { DataModel } from "../model/DataModel.js"
|
|
11
|
+
import { generateIndex } from "./IndexGenerator.js"
|
|
12
|
+
import { generateModels } from "./ModelsGenerator.js"
|
|
13
|
+
import {
|
|
14
|
+
generatePackageJson,
|
|
15
|
+
generateTsconfig,
|
|
16
|
+
generateTsconfigBuild,
|
|
17
|
+
generateTsconfigSrc,
|
|
18
|
+
generateTsconfigTest,
|
|
19
|
+
generateVitestConfig,
|
|
20
|
+
type PackageConfig
|
|
21
|
+
} from "./PackageGenerator.js"
|
|
22
|
+
import { generateQueryModels } from "./QueryModelsGenerator.js"
|
|
23
|
+
import { generateServiceFns } from "./ServiceFnGenerator.js"
|
|
24
|
+
import { generatePromiseServiceFns } from "./ServiceFnPromiseGenerator.js"
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generator configuration.
|
|
28
|
+
*
|
|
29
|
+
* @since 1.0.0
|
|
30
|
+
* @category config
|
|
31
|
+
*/
|
|
32
|
+
export interface GeneratorConfig {
|
|
33
|
+
readonly outputDir: string
|
|
34
|
+
readonly packageName?: string
|
|
35
|
+
readonly serviceName?: string
|
|
36
|
+
readonly force?: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Error thrown during code generation.
|
|
41
|
+
*
|
|
42
|
+
* @since 1.0.0
|
|
43
|
+
* @category errors
|
|
44
|
+
*/
|
|
45
|
+
export class GeneratorError extends Schema.TaggedError<GeneratorError>()(
|
|
46
|
+
"GeneratorError",
|
|
47
|
+
{
|
|
48
|
+
message: Schema.String,
|
|
49
|
+
cause: Schema.optional(Schema.Unknown)
|
|
50
|
+
}
|
|
51
|
+
) {}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generated file result.
|
|
55
|
+
*/
|
|
56
|
+
interface GeneratedFile {
|
|
57
|
+
readonly path: string
|
|
58
|
+
readonly content: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate all code from a data model.
|
|
63
|
+
*
|
|
64
|
+
* @since 1.0.0
|
|
65
|
+
* @category generation
|
|
66
|
+
*/
|
|
67
|
+
export const generate = (
|
|
68
|
+
dataModel: DataModel,
|
|
69
|
+
config: GeneratorConfig
|
|
70
|
+
): Effect.Effect<void, GeneratorError, FileSystem.FileSystem | Path.Path> =>
|
|
71
|
+
Effect.gen(function*() {
|
|
72
|
+
const fs = yield* FileSystem.FileSystem
|
|
73
|
+
const path = yield* Path.Path
|
|
74
|
+
|
|
75
|
+
const outputDir = config.outputDir
|
|
76
|
+
const serviceName = config.serviceName ?? dataModel.serviceName
|
|
77
|
+
const packageName = config.packageName ?? `@template/${serviceName.toLowerCase()}-effect`
|
|
78
|
+
|
|
79
|
+
const packageConfig: PackageConfig = {
|
|
80
|
+
packageName,
|
|
81
|
+
serviceName
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Generate tree-shakable service function files
|
|
85
|
+
const serviceResult = generateServiceFns(dataModel)
|
|
86
|
+
|
|
87
|
+
// Generate Promise-based service function files
|
|
88
|
+
const promiseServiceResult = generatePromiseServiceFns(dataModel)
|
|
89
|
+
|
|
90
|
+
// Generate all files
|
|
91
|
+
const files: Array<GeneratedFile> = [
|
|
92
|
+
// Source files
|
|
93
|
+
{
|
|
94
|
+
path: path.join(outputDir, "src", "Models.ts"),
|
|
95
|
+
content: generateModels(dataModel)
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
path: path.join(outputDir, "src", "QueryModels.ts"),
|
|
99
|
+
content: generateQueryModels(dataModel)
|
|
100
|
+
},
|
|
101
|
+
// Individual entity service function files (tree-shakable)
|
|
102
|
+
...serviceResult.entityServices.map((svc) => ({
|
|
103
|
+
path: path.join(outputDir, "src", svc.fileName),
|
|
104
|
+
content: svc.content
|
|
105
|
+
})),
|
|
106
|
+
// Promise-based entity service function files
|
|
107
|
+
...promiseServiceResult.entityServices.map((svc) => ({
|
|
108
|
+
path: path.join(outputDir, "src", svc.fileName),
|
|
109
|
+
content: svc.content
|
|
110
|
+
})),
|
|
111
|
+
{
|
|
112
|
+
path: path.join(outputDir, "src", "index.ts"),
|
|
113
|
+
content: generateIndex(dataModel)
|
|
114
|
+
},
|
|
115
|
+
// Package configuration files
|
|
116
|
+
{
|
|
117
|
+
path: path.join(outputDir, "package.json"),
|
|
118
|
+
content: generatePackageJson(dataModel, packageConfig)
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
path: path.join(outputDir, "tsconfig.json"),
|
|
122
|
+
content: generateTsconfig()
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
path: path.join(outputDir, "tsconfig.src.json"),
|
|
126
|
+
content: generateTsconfigSrc()
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
path: path.join(outputDir, "tsconfig.test.json"),
|
|
130
|
+
content: generateTsconfigTest()
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
path: path.join(outputDir, "tsconfig.build.json"),
|
|
134
|
+
content: generateTsconfigBuild()
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
path: path.join(outputDir, "vitest.config.ts"),
|
|
138
|
+
content: generateVitestConfig()
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
// Create output directories
|
|
143
|
+
yield* fs.makeDirectory(path.join(outputDir, "src"), { recursive: true }).pipe(
|
|
144
|
+
Effect.mapError((error) =>
|
|
145
|
+
new GeneratorError({
|
|
146
|
+
message: `Failed to create output directory: ${outputDir}/src`,
|
|
147
|
+
cause: error
|
|
148
|
+
})
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
// Write all files
|
|
153
|
+
for (const file of files) {
|
|
154
|
+
// Check if file exists and force is not set
|
|
155
|
+
if (!config.force) {
|
|
156
|
+
const exists = yield* fs.exists(file.path).pipe(
|
|
157
|
+
Effect.mapError(() =>
|
|
158
|
+
new GeneratorError({
|
|
159
|
+
message: `Failed to check file existence: ${file.path}`
|
|
160
|
+
})
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if (exists) {
|
|
165
|
+
yield* Effect.logWarning(`Skipping existing file: ${file.path}`)
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
yield* fs.writeFileString(file.path, file.content).pipe(
|
|
171
|
+
Effect.mapError((error) =>
|
|
172
|
+
new GeneratorError({
|
|
173
|
+
message: `Failed to write file: ${file.path}`,
|
|
174
|
+
cause: error
|
|
175
|
+
})
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
yield* Effect.logInfo(`Generated: ${file.path}`)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
yield* Effect.logInfo(`Generation complete. Output: ${outputDir}`)
|
|
183
|
+
})
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generator for index.ts - Public API exports.
|
|
3
|
+
*
|
|
4
|
+
* @since 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
import type { DataModel } from "../model/DataModel.js"
|
|
7
|
+
import {
|
|
8
|
+
getEditableTypeName,
|
|
9
|
+
getIdTypeName,
|
|
10
|
+
getQueryFactoryName,
|
|
11
|
+
getQueryInstanceName,
|
|
12
|
+
getQueryInterfaceName,
|
|
13
|
+
getServiceClassName
|
|
14
|
+
} from "./NamingHelper.js"
|
|
15
|
+
import { getPromiseServiceName } from "./ServiceFnPromiseGenerator.js"
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generate the index.ts file content.
|
|
19
|
+
*
|
|
20
|
+
* @since 1.0.0
|
|
21
|
+
* @category generation
|
|
22
|
+
*/
|
|
23
|
+
export const generateIndex = (dataModel: DataModel): string => {
|
|
24
|
+
const lines: Array<string> = []
|
|
25
|
+
|
|
26
|
+
// Header
|
|
27
|
+
lines.push(`/**`)
|
|
28
|
+
lines.push(` * Effect-based OData client for ${dataModel.serviceName} service.`)
|
|
29
|
+
lines.push(` * Generated by odata-effect-gen.`)
|
|
30
|
+
lines.push(` *`)
|
|
31
|
+
lines.push(` * @since 1.0.0`)
|
|
32
|
+
lines.push(` */`)
|
|
33
|
+
lines.push(``)
|
|
34
|
+
|
|
35
|
+
// Models
|
|
36
|
+
lines.push(`// Models`)
|
|
37
|
+
lines.push(`export {`)
|
|
38
|
+
|
|
39
|
+
const modelExports: Array<string> = []
|
|
40
|
+
|
|
41
|
+
// Enums
|
|
42
|
+
for (const enumType of dataModel.enumTypes.values()) {
|
|
43
|
+
modelExports.push(enumType.name)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Complex types
|
|
47
|
+
for (const complexType of dataModel.complexTypes.values()) {
|
|
48
|
+
modelExports.push(complexType.name)
|
|
49
|
+
modelExports.push(getEditableTypeName(complexType.name))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Entity types
|
|
53
|
+
for (const entityType of dataModel.entityTypes.values()) {
|
|
54
|
+
modelExports.push(entityType.name)
|
|
55
|
+
if (entityType.keys.length > 0) {
|
|
56
|
+
modelExports.push(getIdTypeName(entityType.name))
|
|
57
|
+
}
|
|
58
|
+
modelExports.push(getEditableTypeName(entityType.name))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < modelExports.length; i++) {
|
|
62
|
+
const isLast = i === modelExports.length - 1
|
|
63
|
+
lines.push(` ${modelExports[i]}${isLast ? "" : ","}`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
lines.push(`} from "./Models.js"`)
|
|
67
|
+
lines.push(``)
|
|
68
|
+
|
|
69
|
+
// Re-export OData infrastructure
|
|
70
|
+
const isV4 = dataModel.version === "V4"
|
|
71
|
+
lines.push(`// Re-export OData infrastructure from @odata-effect/odata-effect`)
|
|
72
|
+
lines.push(`export {`)
|
|
73
|
+
lines.push(` // Errors`)
|
|
74
|
+
if (!isV4) {
|
|
75
|
+
lines.push(` SapErrorDetail,`)
|
|
76
|
+
lines.push(` SapErrorResolution,`)
|
|
77
|
+
lines.push(` SapApplication,`)
|
|
78
|
+
lines.push(` SapInnerError,`)
|
|
79
|
+
lines.push(` SapErrorMessage,`)
|
|
80
|
+
lines.push(` SapErrorBody,`)
|
|
81
|
+
lines.push(` SapErrorResponse,`)
|
|
82
|
+
lines.push(` SapError,`)
|
|
83
|
+
}
|
|
84
|
+
lines.push(` ODataError,`)
|
|
85
|
+
lines.push(` EntityNotFoundError,`)
|
|
86
|
+
lines.push(` ParseError,`)
|
|
87
|
+
if (isV4) {
|
|
88
|
+
// V4 exports
|
|
89
|
+
lines.push(` // OData V4`)
|
|
90
|
+
lines.push(` ODataV4,`)
|
|
91
|
+
lines.push(` ODataV4CollectionResponse,`)
|
|
92
|
+
lines.push(` ODataV4ValueResponse,`)
|
|
93
|
+
lines.push(` ODataV4Annotations,`)
|
|
94
|
+
lines.push(` ODataV4ClientConfig,`)
|
|
95
|
+
lines.push(` buildEntityPathV4,`)
|
|
96
|
+
lines.push(` type ODataV4QueryOptions,`)
|
|
97
|
+
lines.push(` type ODataV4RequestOptions,`)
|
|
98
|
+
lines.push(` type ODataV4ClientConfigService,`)
|
|
99
|
+
lines.push(` type PagedResultV4,`)
|
|
100
|
+
} else {
|
|
101
|
+
// V2 exports
|
|
102
|
+
lines.push(` // OData V2`)
|
|
103
|
+
lines.push(` OData,`)
|
|
104
|
+
lines.push(` ODataSingleResponse,`)
|
|
105
|
+
lines.push(` ODataCollectionResponse,`)
|
|
106
|
+
lines.push(` ODataCollectionResponseWithMeta,`)
|
|
107
|
+
lines.push(` ODataValueResponse,`)
|
|
108
|
+
lines.push(` EntityMetadata,`)
|
|
109
|
+
lines.push(` MediaMetadata,`)
|
|
110
|
+
lines.push(` DeferredContent,`)
|
|
111
|
+
lines.push(` ODataClientConfig,`)
|
|
112
|
+
lines.push(` buildEntityPath,`)
|
|
113
|
+
lines.push(` DEFAULT_HEADERS,`)
|
|
114
|
+
lines.push(` MERGE_HEADERS,`)
|
|
115
|
+
lines.push(` type ODataQueryOptions,`)
|
|
116
|
+
lines.push(` type ODataRequestOptions,`)
|
|
117
|
+
lines.push(` type ODataClientConfigService,`)
|
|
118
|
+
lines.push(` type PagedResult,`)
|
|
119
|
+
}
|
|
120
|
+
lines.push(` // Query Builder`)
|
|
121
|
+
lines.push(` FilterExpression,`)
|
|
122
|
+
lines.push(` StringPath,`)
|
|
123
|
+
lines.push(` NumberPath,`)
|
|
124
|
+
lines.push(` BooleanPath,`)
|
|
125
|
+
lines.push(` DateTimePath,`)
|
|
126
|
+
lines.push(` EntityPath,`)
|
|
127
|
+
lines.push(` CollectionPath,`)
|
|
128
|
+
lines.push(` QueryBuilder,`)
|
|
129
|
+
lines.push(` createQueryPaths,`)
|
|
130
|
+
lines.push(` createQueryBuilder,`)
|
|
131
|
+
lines.push(` type FieldToPath,`)
|
|
132
|
+
lines.push(` type QueryPaths,`)
|
|
133
|
+
lines.push(` type SelectableKeys,`)
|
|
134
|
+
lines.push(` type ExpandableKeys,`)
|
|
135
|
+
lines.push(` type BuiltQuery`)
|
|
136
|
+
lines.push(`} from "@odata-effect/odata-effect"`)
|
|
137
|
+
lines.push(``)
|
|
138
|
+
|
|
139
|
+
// Individual Entity Services (tree-shakable module namespace re-exports)
|
|
140
|
+
lines.push(`// Individual Entity Services (tree-shakable)`)
|
|
141
|
+
for (const entitySet of dataModel.entitySets.values()) {
|
|
142
|
+
const serviceClassName = getServiceClassName(entitySet.name)
|
|
143
|
+
lines.push(`export * as ${serviceClassName} from "./${serviceClassName}.js"`)
|
|
144
|
+
}
|
|
145
|
+
lines.push(``)
|
|
146
|
+
|
|
147
|
+
// Promise-based Entity Services (for non-Effect environments)
|
|
148
|
+
lines.push(`// Promise-based Entity Services (for non-Effect environments)`)
|
|
149
|
+
for (const entitySet of dataModel.entitySets.values()) {
|
|
150
|
+
const promiseServiceName = getPromiseServiceName(entitySet.name)
|
|
151
|
+
lines.push(`export * as ${promiseServiceName} from "./${promiseServiceName}.js"`)
|
|
152
|
+
}
|
|
153
|
+
lines.push(``)
|
|
154
|
+
|
|
155
|
+
// Query Models
|
|
156
|
+
lines.push(`// Query Models`)
|
|
157
|
+
lines.push(`export {`)
|
|
158
|
+
|
|
159
|
+
const queryExports: Array<string> = []
|
|
160
|
+
|
|
161
|
+
// Complex types
|
|
162
|
+
for (const complexType of dataModel.complexTypes.values()) {
|
|
163
|
+
queryExports.push(getQueryInstanceName(complexType.name))
|
|
164
|
+
queryExports.push(`type ${getQueryInterfaceName(complexType.name)}`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Entity types
|
|
168
|
+
for (const entityType of dataModel.entityTypes.values()) {
|
|
169
|
+
queryExports.push(getQueryInstanceName(entityType.name))
|
|
170
|
+
queryExports.push(`type ${getQueryInterfaceName(entityType.name)}`)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Query factories for entity sets
|
|
174
|
+
for (const entitySet of dataModel.entitySets.values()) {
|
|
175
|
+
const entityType = dataModel.entityTypes.get(entitySet.entityTypeFqName)
|
|
176
|
+
if (entityType) {
|
|
177
|
+
queryExports.push(getQueryFactoryName(entityType.name))
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (let i = 0; i < queryExports.length; i++) {
|
|
182
|
+
const isLast = i === queryExports.length - 1
|
|
183
|
+
lines.push(` ${queryExports[i]}${isLast ? "" : ","}`)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
lines.push(`} from "./QueryModels.js"`)
|
|
187
|
+
|
|
188
|
+
return lines.join("\n")
|
|
189
|
+
}
|