@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.
Files changed (131) hide show
  1. package/Cli/package.json +6 -0
  2. package/LICENSE +21 -0
  3. package/README.md +37 -0
  4. package/bin/package.json +6 -0
  5. package/digester/Digester/package.json +6 -0
  6. package/digester/TypeMapper/package.json +6 -0
  7. package/dist/cjs/Cli.js +95 -0
  8. package/dist/cjs/Cli.js.map +1 -0
  9. package/dist/cjs/bin.js +14 -0
  10. package/dist/cjs/bin.js.map +1 -0
  11. package/dist/cjs/digester/Digester.js +488 -0
  12. package/dist/cjs/digester/Digester.js.map +1 -0
  13. package/dist/cjs/digester/TypeMapper.js +262 -0
  14. package/dist/cjs/digester/TypeMapper.js.map +1 -0
  15. package/dist/cjs/generator/Generator.js +126 -0
  16. package/dist/cjs/generator/Generator.js.map +1 -0
  17. package/dist/cjs/generator/IndexGenerator.js +164 -0
  18. package/dist/cjs/generator/IndexGenerator.js.map +1 -0
  19. package/dist/cjs/generator/ModelsGenerator.js +255 -0
  20. package/dist/cjs/generator/ModelsGenerator.js.map +1 -0
  21. package/dist/cjs/generator/NamingHelper.js +164 -0
  22. package/dist/cjs/generator/NamingHelper.js.map +1 -0
  23. package/dist/cjs/generator/PackageGenerator.js +168 -0
  24. package/dist/cjs/generator/PackageGenerator.js.map +1 -0
  25. package/dist/cjs/generator/QueryModelsGenerator.js +182 -0
  26. package/dist/cjs/generator/QueryModelsGenerator.js.map +1 -0
  27. package/dist/cjs/generator/ServiceFnGenerator.js +258 -0
  28. package/dist/cjs/generator/ServiceFnGenerator.js.map +1 -0
  29. package/dist/cjs/generator/ServiceFnPromiseGenerator.js +182 -0
  30. package/dist/cjs/generator/ServiceFnPromiseGenerator.js.map +1 -0
  31. package/dist/cjs/index.js +38 -0
  32. package/dist/cjs/index.js.map +1 -0
  33. package/dist/cjs/model/DataModel.js +28 -0
  34. package/dist/cjs/model/DataModel.js.map +1 -0
  35. package/dist/cjs/parser/EdmxSchema.js +20 -0
  36. package/dist/cjs/parser/EdmxSchema.js.map +1 -0
  37. package/dist/cjs/parser/XmlParser.js +47 -0
  38. package/dist/cjs/parser/XmlParser.js.map +1 -0
  39. package/dist/dts/Cli.d.ts +9 -0
  40. package/dist/dts/Cli.d.ts.map +1 -0
  41. package/dist/dts/bin.d.ts +3 -0
  42. package/dist/dts/bin.d.ts.map +1 -0
  43. package/dist/dts/digester/Digester.d.ts +33 -0
  44. package/dist/dts/digester/Digester.d.ts.map +1 -0
  45. package/dist/dts/digester/TypeMapper.d.ts +79 -0
  46. package/dist/dts/digester/TypeMapper.d.ts.map +1 -0
  47. package/dist/dts/generator/Generator.d.ts +45 -0
  48. package/dist/dts/generator/Generator.d.ts.map +1 -0
  49. package/dist/dts/generator/IndexGenerator.d.ts +14 -0
  50. package/dist/dts/generator/IndexGenerator.d.ts.map +1 -0
  51. package/dist/dts/generator/ModelsGenerator.d.ts +14 -0
  52. package/dist/dts/generator/ModelsGenerator.d.ts.map +1 -0
  53. package/dist/dts/generator/NamingHelper.d.ts +110 -0
  54. package/dist/dts/generator/NamingHelper.d.ts.map +1 -0
  55. package/dist/dts/generator/PackageGenerator.d.ts +53 -0
  56. package/dist/dts/generator/PackageGenerator.d.ts.map +1 -0
  57. package/dist/dts/generator/QueryModelsGenerator.d.ts +14 -0
  58. package/dist/dts/generator/QueryModelsGenerator.d.ts.map +1 -0
  59. package/dist/dts/generator/ServiceFnGenerator.d.ts +37 -0
  60. package/dist/dts/generator/ServiceFnGenerator.d.ts.map +1 -0
  61. package/dist/dts/generator/ServiceFnPromiseGenerator.d.ts +40 -0
  62. package/dist/dts/generator/ServiceFnPromiseGenerator.d.ts.map +1 -0
  63. package/dist/dts/index.d.ts +101 -0
  64. package/dist/dts/index.d.ts.map +1 -0
  65. package/dist/dts/model/DataModel.d.ts +188 -0
  66. package/dist/dts/model/DataModel.d.ts.map +1 -0
  67. package/dist/dts/parser/EdmxSchema.d.ts +228 -0
  68. package/dist/dts/parser/EdmxSchema.d.ts.map +1 -0
  69. package/dist/dts/parser/XmlParser.d.ts +31 -0
  70. package/dist/dts/parser/XmlParser.d.ts.map +1 -0
  71. package/dist/esm/Cli.js +87 -0
  72. package/dist/esm/Cli.js.map +1 -0
  73. package/dist/esm/bin.js +12 -0
  74. package/dist/esm/bin.js.map +1 -0
  75. package/dist/esm/digester/Digester.js +478 -0
  76. package/dist/esm/digester/Digester.js.map +1 -0
  77. package/dist/esm/digester/TypeMapper.js +248 -0
  78. package/dist/esm/digester/TypeMapper.js.map +1 -0
  79. package/dist/esm/generator/Generator.js +116 -0
  80. package/dist/esm/generator/Generator.js.map +1 -0
  81. package/dist/esm/generator/IndexGenerator.js +157 -0
  82. package/dist/esm/generator/IndexGenerator.js.map +1 -0
  83. package/dist/esm/generator/ModelsGenerator.js +248 -0
  84. package/dist/esm/generator/ModelsGenerator.js.map +1 -0
  85. package/dist/esm/generator/NamingHelper.js +147 -0
  86. package/dist/esm/generator/NamingHelper.js.map +1 -0
  87. package/dist/esm/generator/PackageGenerator.js +156 -0
  88. package/dist/esm/generator/PackageGenerator.js.map +1 -0
  89. package/dist/esm/generator/QueryModelsGenerator.js +175 -0
  90. package/dist/esm/generator/QueryModelsGenerator.js.map +1 -0
  91. package/dist/esm/generator/ServiceFnGenerator.js +251 -0
  92. package/dist/esm/generator/ServiceFnGenerator.js.map +1 -0
  93. package/dist/esm/generator/ServiceFnPromiseGenerator.js +174 -0
  94. package/dist/esm/generator/ServiceFnPromiseGenerator.js.map +1 -0
  95. package/dist/esm/index.js +101 -0
  96. package/dist/esm/index.js.map +1 -0
  97. package/dist/esm/model/DataModel.js +21 -0
  98. package/dist/esm/model/DataModel.js.map +1 -0
  99. package/dist/esm/package.json +4 -0
  100. package/dist/esm/parser/EdmxSchema.js +13 -0
  101. package/dist/esm/parser/EdmxSchema.js.map +1 -0
  102. package/dist/esm/parser/XmlParser.js +37 -0
  103. package/dist/esm/parser/XmlParser.js.map +1 -0
  104. package/generator/Generator/package.json +6 -0
  105. package/generator/IndexGenerator/package.json +6 -0
  106. package/generator/ModelsGenerator/package.json +6 -0
  107. package/generator/NamingHelper/package.json +6 -0
  108. package/generator/PackageGenerator/package.json +6 -0
  109. package/generator/QueryModelsGenerator/package.json +6 -0
  110. package/generator/ServiceFnGenerator/package.json +6 -0
  111. package/generator/ServiceFnPromiseGenerator/package.json +6 -0
  112. package/model/DataModel/package.json +6 -0
  113. package/package.json +157 -0
  114. package/parser/EdmxSchema/package.json +6 -0
  115. package/parser/XmlParser/package.json +6 -0
  116. package/src/Cli.ts +115 -0
  117. package/src/bin.ts +17 -0
  118. package/src/digester/Digester.ts +600 -0
  119. package/src/digester/TypeMapper.ts +181 -0
  120. package/src/generator/Generator.ts +183 -0
  121. package/src/generator/IndexGenerator.ts +189 -0
  122. package/src/generator/ModelsGenerator.ts +344 -0
  123. package/src/generator/NamingHelper.ts +159 -0
  124. package/src/generator/PackageGenerator.ts +176 -0
  125. package/src/generator/QueryModelsGenerator.ts +228 -0
  126. package/src/generator/ServiceFnGenerator.ts +324 -0
  127. package/src/generator/ServiceFnPromiseGenerator.ts +242 -0
  128. package/src/index.ts +114 -0
  129. package/src/model/DataModel.ts +254 -0
  130. package/src/parser/EdmxSchema.ts +308 -0
  131. 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
+ }