@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,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
+ }