@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,600 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Digester for converting parsed EDMX into a DataModel.
|
|
3
|
+
* Supports both OData V2 and V4.
|
|
4
|
+
*
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import * as Schema from "effect/Schema"
|
|
9
|
+
import { getClassName, getPropertyName } from "../generator/NamingHelper.js"
|
|
10
|
+
import {
|
|
11
|
+
type ComplexTypeModel,
|
|
12
|
+
createDataModel,
|
|
13
|
+
type DataModel,
|
|
14
|
+
type EntitySetModel,
|
|
15
|
+
type EntityTypeModel,
|
|
16
|
+
type EnumTypeModel,
|
|
17
|
+
type NavigationPropertyModel,
|
|
18
|
+
type OperationModel,
|
|
19
|
+
type OperationParameterModel,
|
|
20
|
+
type PropertyModel,
|
|
21
|
+
type SingletonModel
|
|
22
|
+
} from "../model/DataModel.js"
|
|
23
|
+
import { detectODataVersion } from "../parser/EdmxSchema.js"
|
|
24
|
+
import type {
|
|
25
|
+
Association,
|
|
26
|
+
ComplexType as EdmxComplexType,
|
|
27
|
+
EntityContainer,
|
|
28
|
+
EntityType,
|
|
29
|
+
EnumType,
|
|
30
|
+
NavigationProperty,
|
|
31
|
+
NavigationPropertyV2,
|
|
32
|
+
NavigationPropertyV4,
|
|
33
|
+
ODataEdmxModel,
|
|
34
|
+
ODataVersion,
|
|
35
|
+
Operation,
|
|
36
|
+
Property,
|
|
37
|
+
Schema as EdmxSchema
|
|
38
|
+
} from "../parser/EdmxSchema.js"
|
|
39
|
+
import {
|
|
40
|
+
getComplexTypeMapping,
|
|
41
|
+
getEnumTypeMapping,
|
|
42
|
+
getPrimitiveTypeMapping,
|
|
43
|
+
getSimpleTypeName,
|
|
44
|
+
isPrimitiveType,
|
|
45
|
+
parseODataType
|
|
46
|
+
} from "./TypeMapper.js"
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Error thrown during metadata digestion.
|
|
50
|
+
*
|
|
51
|
+
* @since 1.0.0
|
|
52
|
+
* @category errors
|
|
53
|
+
*/
|
|
54
|
+
export class DigestError extends Schema.TaggedError<DigestError>()(
|
|
55
|
+
"DigestError",
|
|
56
|
+
{
|
|
57
|
+
message: Schema.String,
|
|
58
|
+
cause: Schema.optional(Schema.Unknown)
|
|
59
|
+
}
|
|
60
|
+
) {}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Context for digestion including type resolution.
|
|
64
|
+
*/
|
|
65
|
+
interface DigestContext {
|
|
66
|
+
readonly version: ODataVersion
|
|
67
|
+
readonly namespace: string
|
|
68
|
+
readonly associations: Map<string, Association>
|
|
69
|
+
readonly enumTypes: Set<string>
|
|
70
|
+
readonly complexTypes: Set<string>
|
|
71
|
+
readonly entityTypes: Set<string>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Digest OData metadata into a DataModel.
|
|
76
|
+
*
|
|
77
|
+
* @since 1.0.0
|
|
78
|
+
* @category digestion
|
|
79
|
+
*/
|
|
80
|
+
export const digestMetadata = (
|
|
81
|
+
edmx: ODataEdmxModel
|
|
82
|
+
): Effect.Effect<DataModel, DigestError> =>
|
|
83
|
+
Effect.try({
|
|
84
|
+
try: () => {
|
|
85
|
+
const version = detectODataVersion(edmx)
|
|
86
|
+
const dataServices = edmx["edmx:Edmx"]["edmx:DataServices"][0]
|
|
87
|
+
const schemas = dataServices.Schema
|
|
88
|
+
|
|
89
|
+
if (!schemas || schemas.length === 0) {
|
|
90
|
+
throw new Error("No schemas found in metadata")
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Use the first schema's namespace as the main namespace
|
|
94
|
+
const mainSchema = schemas[0]
|
|
95
|
+
const namespace = mainSchema.$.Namespace
|
|
96
|
+
|
|
97
|
+
// Find entity container
|
|
98
|
+
let entityContainer: EntityContainer | undefined
|
|
99
|
+
for (const schema of schemas) {
|
|
100
|
+
if (schema.EntityContainer && schema.EntityContainer.length > 0) {
|
|
101
|
+
entityContainer = schema.EntityContainer[0]
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const serviceName = entityContainer?.$.Name ?? "ODataService"
|
|
107
|
+
const dataModel = createDataModel(version, namespace, serviceName)
|
|
108
|
+
|
|
109
|
+
// Build context for type resolution
|
|
110
|
+
const context = buildContext(version, namespace, schemas)
|
|
111
|
+
|
|
112
|
+
// First pass: collect all type names
|
|
113
|
+
for (const schema of schemas) {
|
|
114
|
+
collectTypeNames(schema, context)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Second pass: digest all types
|
|
118
|
+
for (const schema of schemas) {
|
|
119
|
+
digestSchema(schema, dataModel, context)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Digest entity container
|
|
123
|
+
if (entityContainer) {
|
|
124
|
+
digestEntityContainer(entityContainer, dataModel, context)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return dataModel
|
|
128
|
+
},
|
|
129
|
+
catch: (error) =>
|
|
130
|
+
new DigestError({
|
|
131
|
+
message: error instanceof Error ? error.message : "Failed to digest metadata",
|
|
132
|
+
cause: error
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Build digestion context from schemas.
|
|
138
|
+
*/
|
|
139
|
+
const buildContext = (
|
|
140
|
+
version: ODataVersion,
|
|
141
|
+
namespace: string,
|
|
142
|
+
schemas: ReadonlyArray<EdmxSchema>
|
|
143
|
+
): DigestContext => {
|
|
144
|
+
const associations = new Map<string, Association>()
|
|
145
|
+
|
|
146
|
+
// Collect V2 associations
|
|
147
|
+
for (const schema of schemas) {
|
|
148
|
+
if (schema.Association) {
|
|
149
|
+
for (const assoc of schema.Association) {
|
|
150
|
+
const fqName = `${schema.$.Namespace}.${assoc.$.Name}`
|
|
151
|
+
associations.set(fqName, assoc)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
version,
|
|
158
|
+
namespace,
|
|
159
|
+
associations,
|
|
160
|
+
enumTypes: new Set(),
|
|
161
|
+
complexTypes: new Set(),
|
|
162
|
+
entityTypes: new Set()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* First pass: collect all type names for resolution.
|
|
168
|
+
*/
|
|
169
|
+
const collectTypeNames = (schema: EdmxSchema, context: DigestContext): void => {
|
|
170
|
+
const ns = schema.$.Namespace
|
|
171
|
+
|
|
172
|
+
if (schema.EnumType) {
|
|
173
|
+
for (const enumType of schema.EnumType) {
|
|
174
|
+
context.enumTypes.add(`${ns}.${enumType.$.Name}`)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (schema.ComplexType) {
|
|
179
|
+
for (const complexType of schema.ComplexType) {
|
|
180
|
+
context.complexTypes.add(`${ns}.${complexType.$.Name}`)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (schema.EntityType) {
|
|
185
|
+
for (const entityType of schema.EntityType) {
|
|
186
|
+
context.entityTypes.add(`${ns}.${entityType.$.Name}`)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Digest a single schema.
|
|
193
|
+
*/
|
|
194
|
+
const digestSchema = (
|
|
195
|
+
schema: EdmxSchema,
|
|
196
|
+
dataModel: DataModel,
|
|
197
|
+
context: DigestContext
|
|
198
|
+
): void => {
|
|
199
|
+
const ns = schema.$.Namespace
|
|
200
|
+
|
|
201
|
+
// Digest enum types
|
|
202
|
+
if (schema.EnumType) {
|
|
203
|
+
for (const enumType of schema.EnumType) {
|
|
204
|
+
const model = digestEnumType(enumType, ns)
|
|
205
|
+
dataModel.enumTypes.set(model.fqName, model)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Digest complex types
|
|
210
|
+
if (schema.ComplexType) {
|
|
211
|
+
for (const complexType of schema.ComplexType) {
|
|
212
|
+
const model = digestComplexType(complexType, ns, context)
|
|
213
|
+
dataModel.complexTypes.set(model.fqName, model)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Digest entity types
|
|
218
|
+
if (schema.EntityType) {
|
|
219
|
+
for (const entityType of schema.EntityType) {
|
|
220
|
+
const model = digestEntityType(entityType, ns, context)
|
|
221
|
+
dataModel.entityTypes.set(model.fqName, model)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Digest operations (V4 functions and actions)
|
|
226
|
+
if (schema.Function) {
|
|
227
|
+
for (const func of schema.Function) {
|
|
228
|
+
const model = digestOperation(func, ns, "Function", context)
|
|
229
|
+
dataModel.operations.set(model.fqName, model)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (schema.Action) {
|
|
234
|
+
for (const action of schema.Action) {
|
|
235
|
+
const model = digestOperation(action, ns, "Action", context)
|
|
236
|
+
dataModel.operations.set(model.fqName, model)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Digest an enum type.
|
|
243
|
+
*/
|
|
244
|
+
const digestEnumType = (enumType: EnumType, namespace: string): EnumTypeModel => {
|
|
245
|
+
const name = enumType.$.Name
|
|
246
|
+
const members = enumType.Member.map((member, index) => ({
|
|
247
|
+
name: member.$.Name,
|
|
248
|
+
value: member.$.Value !== undefined ? parseInt(member.$.Value, 10) : index
|
|
249
|
+
}))
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
fqName: `${namespace}.${name}`,
|
|
253
|
+
odataName: name,
|
|
254
|
+
name: getClassName(name),
|
|
255
|
+
members,
|
|
256
|
+
isFlags: enumType.$.IsFlags === "true"
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Digest a complex type.
|
|
262
|
+
*/
|
|
263
|
+
const digestComplexType = (
|
|
264
|
+
complexType: EdmxComplexType,
|
|
265
|
+
namespace: string,
|
|
266
|
+
context: DigestContext
|
|
267
|
+
): ComplexTypeModel => {
|
|
268
|
+
const name = complexType.$.Name
|
|
269
|
+
const properties = (complexType.Property ?? []).map((p) => digestProperty(p, [], context))
|
|
270
|
+
const navigationProperties = (complexType.NavigationProperty ?? []).map((np) => digestNavigationProperty(np, context))
|
|
271
|
+
|
|
272
|
+
const result: ComplexTypeModel = {
|
|
273
|
+
fqName: `${namespace}.${name}`,
|
|
274
|
+
odataName: name,
|
|
275
|
+
name: getClassName(name),
|
|
276
|
+
properties,
|
|
277
|
+
navigationProperties,
|
|
278
|
+
isAbstract: complexType.$.Abstract === "true",
|
|
279
|
+
isOpen: complexType.$.OpenType === "true"
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (complexType.$.BaseType) {
|
|
283
|
+
return { ...result, baseType: complexType.$.BaseType }
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return result
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Digest an entity type.
|
|
291
|
+
*/
|
|
292
|
+
const digestEntityType = (
|
|
293
|
+
entityType: EntityType,
|
|
294
|
+
namespace: string,
|
|
295
|
+
context: DigestContext
|
|
296
|
+
): EntityTypeModel => {
|
|
297
|
+
const name = entityType.$.Name
|
|
298
|
+
const keyNames = new Set<string>()
|
|
299
|
+
|
|
300
|
+
// Collect key property names
|
|
301
|
+
if (entityType.Key && entityType.Key[0]?.PropertyRef) {
|
|
302
|
+
for (const keyRef of entityType.Key[0].PropertyRef) {
|
|
303
|
+
keyNames.add(keyRef.$.Name)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const properties = (entityType.Property ?? []).map((p) => digestProperty(p, Array.from(keyNames), context))
|
|
308
|
+
|
|
309
|
+
const keys = properties.filter((p) => p.isKey)
|
|
310
|
+
const navigationProperties = (entityType.NavigationProperty ?? []).map((np) => digestNavigationProperty(np, context))
|
|
311
|
+
|
|
312
|
+
const result: EntityTypeModel = {
|
|
313
|
+
fqName: `${namespace}.${name}`,
|
|
314
|
+
odataName: name,
|
|
315
|
+
name: getClassName(name),
|
|
316
|
+
keys,
|
|
317
|
+
properties,
|
|
318
|
+
navigationProperties,
|
|
319
|
+
isAbstract: entityType.$.Abstract === "true",
|
|
320
|
+
isOpen: entityType.$.OpenType === "true"
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (entityType.$.BaseType) {
|
|
324
|
+
return { ...result, baseType: entityType.$.BaseType }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return result
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Digest a property.
|
|
332
|
+
*/
|
|
333
|
+
const digestProperty = (
|
|
334
|
+
property: Property,
|
|
335
|
+
keyNames: ReadonlyArray<string>,
|
|
336
|
+
context: DigestContext
|
|
337
|
+
): PropertyModel => {
|
|
338
|
+
const odataName = property.$.Name
|
|
339
|
+
const odataType = property.$.Type
|
|
340
|
+
const { baseType, isCollection } = parseODataType(odataType)
|
|
341
|
+
const isKey = keyNames.includes(odataName)
|
|
342
|
+
const isNullable = property.$.Nullable !== "false" && !isKey
|
|
343
|
+
|
|
344
|
+
const typeMapping = resolveTypeMapping(baseType, context)
|
|
345
|
+
|
|
346
|
+
const result: PropertyModel = {
|
|
347
|
+
odataName,
|
|
348
|
+
name: getPropertyName(odataName),
|
|
349
|
+
odataType,
|
|
350
|
+
typeMapping,
|
|
351
|
+
isCollection,
|
|
352
|
+
isNullable,
|
|
353
|
+
isKey
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const withMaxLength = property.$.MaxLength
|
|
357
|
+
? { ...result, maxLength: parseInt(property.$.MaxLength, 10) }
|
|
358
|
+
: result
|
|
359
|
+
const withPrecision = property.$.Precision
|
|
360
|
+
? { ...withMaxLength, precision: parseInt(property.$.Precision, 10) }
|
|
361
|
+
: withMaxLength
|
|
362
|
+
const withScale = property.$.Scale
|
|
363
|
+
? { ...withPrecision, scale: parseInt(property.$.Scale, 10) }
|
|
364
|
+
: withPrecision
|
|
365
|
+
|
|
366
|
+
return withScale
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Check if a navigation property is V4 style (has Type attribute).
|
|
371
|
+
*/
|
|
372
|
+
const isV4NavigationProperty = (
|
|
373
|
+
navProp: NavigationProperty
|
|
374
|
+
): navProp is NavigationPropertyV4 => {
|
|
375
|
+
return "Type" in navProp.$
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Digest a navigation property.
|
|
380
|
+
*/
|
|
381
|
+
const digestNavigationProperty = (
|
|
382
|
+
navProp: NavigationProperty,
|
|
383
|
+
context: DigestContext
|
|
384
|
+
): NavigationPropertyModel => {
|
|
385
|
+
const odataName = navProp.$.Name
|
|
386
|
+
|
|
387
|
+
// V4 style navigation property (has Type attribute)
|
|
388
|
+
if (isV4NavigationProperty(navProp)) {
|
|
389
|
+
const { baseType, isCollection } = parseODataType(navProp.$.Type)
|
|
390
|
+
const targetTypeName = getSimpleTypeName(baseType)
|
|
391
|
+
|
|
392
|
+
const result: NavigationPropertyModel = {
|
|
393
|
+
odataName,
|
|
394
|
+
name: getPropertyName(odataName),
|
|
395
|
+
targetType: targetTypeName,
|
|
396
|
+
isCollection,
|
|
397
|
+
isNullable: navProp.$.Nullable !== "false"
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (navProp.$.Partner) {
|
|
401
|
+
return { ...result, partner: navProp.$.Partner }
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return result
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// V2 style navigation property (uses Relationship)
|
|
408
|
+
const v2NavProp = navProp as NavigationPropertyV2
|
|
409
|
+
const relationship = v2NavProp.$.Relationship
|
|
410
|
+
const toRole = v2NavProp.$.ToRole
|
|
411
|
+
const association = context.associations.get(relationship)
|
|
412
|
+
|
|
413
|
+
let targetType = "unknown"
|
|
414
|
+
let isCollection = false
|
|
415
|
+
|
|
416
|
+
if (association) {
|
|
417
|
+
const targetEnd = association.End.find((end) => end.$.Role === toRole)
|
|
418
|
+
if (targetEnd) {
|
|
419
|
+
targetType = getSimpleTypeName(targetEnd.$.Type)
|
|
420
|
+
isCollection = targetEnd.$.Multiplicity === "*"
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
odataName,
|
|
426
|
+
name: getPropertyName(odataName),
|
|
427
|
+
targetType,
|
|
428
|
+
isCollection,
|
|
429
|
+
isNullable: true
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Digest entity container.
|
|
435
|
+
*/
|
|
436
|
+
const digestEntityContainer = (
|
|
437
|
+
container: EntityContainer,
|
|
438
|
+
dataModel: DataModel,
|
|
439
|
+
context: DigestContext
|
|
440
|
+
): void => {
|
|
441
|
+
// Digest entity sets
|
|
442
|
+
if (container.EntitySet) {
|
|
443
|
+
for (const entitySet of container.EntitySet) {
|
|
444
|
+
const model: EntitySetModel = {
|
|
445
|
+
name: entitySet.$.Name,
|
|
446
|
+
entityTypeFqName: entitySet.$.EntityType,
|
|
447
|
+
entityTypeName: getSimpleTypeName(entitySet.$.EntityType)
|
|
448
|
+
}
|
|
449
|
+
dataModel.entitySets.set(model.name, model)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Digest singletons (V4)
|
|
454
|
+
if (container.Singleton) {
|
|
455
|
+
for (const singleton of container.Singleton) {
|
|
456
|
+
const model: SingletonModel = {
|
|
457
|
+
name: singleton.$.Name,
|
|
458
|
+
typeFqName: singleton.$.Type,
|
|
459
|
+
typeName: getSimpleTypeName(singleton.$.Type)
|
|
460
|
+
}
|
|
461
|
+
dataModel.singletons.set(model.name, model)
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Digest V2 function imports
|
|
466
|
+
if (container.FunctionImport) {
|
|
467
|
+
for (const funcImport of container.FunctionImport) {
|
|
468
|
+
// Only process V2 function imports (no Function reference)
|
|
469
|
+
if (!funcImport.$.Function) {
|
|
470
|
+
const params: Array<OperationParameterModel> = (funcImport.Parameter ?? []).map((p) => {
|
|
471
|
+
const { baseType, isCollection } = parseODataType(p.$.Type)
|
|
472
|
+
return {
|
|
473
|
+
name: getPropertyName(p.$.Name),
|
|
474
|
+
odataType: p.$.Type,
|
|
475
|
+
typeMapping: resolveTypeMapping(baseType, context),
|
|
476
|
+
isNullable: p.$.Nullable !== "false",
|
|
477
|
+
isCollection
|
|
478
|
+
}
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
let model: OperationModel = {
|
|
482
|
+
fqName: `${context.namespace}.${funcImport.$.Name}`,
|
|
483
|
+
odataName: funcImport.$.Name,
|
|
484
|
+
name: getPropertyName(funcImport.$.Name),
|
|
485
|
+
type: "Function",
|
|
486
|
+
isBound: false,
|
|
487
|
+
parameters: params
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (funcImport.$.ReturnType) {
|
|
491
|
+
const { baseType, isCollection } = parseODataType(funcImport.$.ReturnType)
|
|
492
|
+
model = {
|
|
493
|
+
...model,
|
|
494
|
+
returnType: {
|
|
495
|
+
odataType: funcImport.$.ReturnType,
|
|
496
|
+
typeMapping: resolveTypeMapping(baseType, context),
|
|
497
|
+
isCollection,
|
|
498
|
+
isNullable: false
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (funcImport.$.EntitySet) {
|
|
504
|
+
model = { ...model, entitySetPath: funcImport.$.EntitySet }
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
dataModel.operations.set(model.fqName, model)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Digest an operation (Function or Action).
|
|
515
|
+
*/
|
|
516
|
+
const digestOperation = (
|
|
517
|
+
operation: Operation,
|
|
518
|
+
namespace: string,
|
|
519
|
+
type: "Function" | "Action",
|
|
520
|
+
context: DigestContext
|
|
521
|
+
): OperationModel => {
|
|
522
|
+
const name = operation.$.Name
|
|
523
|
+
const isBound = operation.$.IsBound === "true"
|
|
524
|
+
|
|
525
|
+
const allParams = (operation.Parameter ?? []).map((p) => {
|
|
526
|
+
const { baseType, isCollection } = parseODataType(p.$.Type)
|
|
527
|
+
return {
|
|
528
|
+
name: getPropertyName(p.$.Name),
|
|
529
|
+
odataType: p.$.Type,
|
|
530
|
+
typeMapping: resolveTypeMapping(baseType, context),
|
|
531
|
+
isNullable: p.$.Nullable !== "false",
|
|
532
|
+
isCollection
|
|
533
|
+
}
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
// First parameter is binding parameter if bound
|
|
537
|
+
const bindingParameter = isBound && allParams.length > 0 ? allParams[0] : undefined
|
|
538
|
+
const parameters = isBound ? allParams.slice(1) : allParams
|
|
539
|
+
|
|
540
|
+
let model: OperationModel = {
|
|
541
|
+
fqName: `${namespace}.${name}`,
|
|
542
|
+
odataName: name,
|
|
543
|
+
name: getPropertyName(name),
|
|
544
|
+
type,
|
|
545
|
+
isBound,
|
|
546
|
+
parameters
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (bindingParameter) {
|
|
550
|
+
model = { ...model, bindingParameter }
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (operation.ReturnType && operation.ReturnType.length > 0) {
|
|
554
|
+
const rt = operation.ReturnType[0]
|
|
555
|
+
const typeStr = rt.$?.Type ?? rt.Type ?? ""
|
|
556
|
+
if (typeStr) {
|
|
557
|
+
const { baseType, isCollection } = parseODataType(typeStr)
|
|
558
|
+
model = {
|
|
559
|
+
...model,
|
|
560
|
+
returnType: {
|
|
561
|
+
odataType: typeStr,
|
|
562
|
+
typeMapping: resolveTypeMapping(baseType, context),
|
|
563
|
+
isCollection,
|
|
564
|
+
isNullable: rt.$?.Nullable !== "false"
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (operation.$.EntitySetPath) {
|
|
571
|
+
model = { ...model, entitySetPath: operation.$.EntitySetPath }
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return model
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Resolve type mapping based on context.
|
|
579
|
+
*/
|
|
580
|
+
const resolveTypeMapping = (
|
|
581
|
+
type: string,
|
|
582
|
+
context: DigestContext
|
|
583
|
+
) => {
|
|
584
|
+
if (isPrimitiveType(type)) {
|
|
585
|
+
return getPrimitiveTypeMapping(context.version, type)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const simpleTypeName = getSimpleTypeName(type)
|
|
589
|
+
|
|
590
|
+
if (context.enumTypes.has(type)) {
|
|
591
|
+
return getEnumTypeMapping(getClassName(simpleTypeName))
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (context.complexTypes.has(type) || context.entityTypes.has(type)) {
|
|
595
|
+
return getComplexTypeMapping(getClassName(simpleTypeName))
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Default to complex type mapping for unknown types
|
|
599
|
+
return getComplexTypeMapping(getClassName(simpleTypeName))
|
|
600
|
+
}
|