@odata-effect/odata-effect-generator 0.1.2 → 0.3.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 (74) hide show
  1. package/README.md +232 -5
  2. package/dist/cjs/Cli.js +5 -1
  3. package/dist/cjs/Cli.js.map +1 -1
  4. package/dist/cjs/generator/Generator.js +35 -25
  5. package/dist/cjs/generator/Generator.js.map +1 -1
  6. package/dist/cjs/generator/IndexGenerator.js +29 -80
  7. package/dist/cjs/generator/IndexGenerator.js.map +1 -1
  8. package/dist/cjs/generator/ModelsGenerator.js +2 -5
  9. package/dist/cjs/generator/ModelsGenerator.js.map +1 -1
  10. package/dist/cjs/generator/NamingHelper.js +4 -1
  11. package/dist/cjs/generator/NamingHelper.js.map +1 -1
  12. package/dist/cjs/generator/NavigationGenerator.js +370 -0
  13. package/dist/cjs/generator/NavigationGenerator.js.map +1 -0
  14. package/dist/cjs/generator/OperationsGenerator.js +384 -0
  15. package/dist/cjs/generator/OperationsGenerator.js.map +1 -0
  16. package/dist/cjs/generator/QueryModelsGenerator.js +2 -2
  17. package/dist/cjs/generator/QueryModelsGenerator.js.map +1 -1
  18. package/dist/cjs/generator/ServiceFnGenerator.js +102 -224
  19. package/dist/cjs/generator/ServiceFnGenerator.js.map +1 -1
  20. package/dist/cjs/index.js +3 -3
  21. package/dist/dts/Cli.d.ts.map +1 -1
  22. package/dist/dts/generator/Generator.d.ts +2 -0
  23. package/dist/dts/generator/Generator.d.ts.map +1 -1
  24. package/dist/dts/generator/IndexGenerator.d.ts.map +1 -1
  25. package/dist/dts/generator/NamingHelper.d.ts.map +1 -1
  26. package/dist/dts/generator/NavigationGenerator.d.ts +55 -0
  27. package/dist/dts/generator/NavigationGenerator.d.ts.map +1 -0
  28. package/dist/dts/generator/OperationsGenerator.d.ts +50 -0
  29. package/dist/dts/generator/OperationsGenerator.d.ts.map +1 -0
  30. package/dist/dts/generator/ServiceFnGenerator.d.ts +10 -11
  31. package/dist/dts/generator/ServiceFnGenerator.d.ts.map +1 -1
  32. package/dist/dts/index.d.ts +4 -4
  33. package/dist/dts/index.d.ts.map +1 -1
  34. package/dist/esm/Cli.js +5 -1
  35. package/dist/esm/Cli.js.map +1 -1
  36. package/dist/esm/generator/Generator.js +35 -25
  37. package/dist/esm/generator/Generator.js.map +1 -1
  38. package/dist/esm/generator/IndexGenerator.js +29 -80
  39. package/dist/esm/generator/IndexGenerator.js.map +1 -1
  40. package/dist/esm/generator/ModelsGenerator.js +2 -5
  41. package/dist/esm/generator/ModelsGenerator.js.map +1 -1
  42. package/dist/esm/generator/NamingHelper.js +4 -1
  43. package/dist/esm/generator/NamingHelper.js.map +1 -1
  44. package/dist/esm/generator/NavigationGenerator.js +362 -0
  45. package/dist/esm/generator/NavigationGenerator.js.map +1 -0
  46. package/dist/esm/generator/OperationsGenerator.js +375 -0
  47. package/dist/esm/generator/OperationsGenerator.js.map +1 -0
  48. package/dist/esm/generator/QueryModelsGenerator.js +2 -2
  49. package/dist/esm/generator/QueryModelsGenerator.js.map +1 -1
  50. package/dist/esm/generator/ServiceFnGenerator.js +102 -224
  51. package/dist/esm/generator/ServiceFnGenerator.js.map +1 -1
  52. package/dist/esm/index.js +4 -4
  53. package/dist/esm/index.js.map +1 -1
  54. package/generator/NavigationGenerator/package.json +6 -0
  55. package/generator/OperationsGenerator/package.json +6 -0
  56. package/package.json +17 -9
  57. package/src/Cli.ts +15 -2
  58. package/src/generator/Generator.ts +44 -23
  59. package/src/generator/IndexGenerator.ts +29 -80
  60. package/src/generator/ModelsGenerator.ts +2 -5
  61. package/src/generator/NamingHelper.ts +8 -1
  62. package/src/generator/NavigationGenerator.ts +485 -0
  63. package/src/generator/OperationsGenerator.ts +507 -0
  64. package/src/generator/QueryModelsGenerator.ts +2 -2
  65. package/src/generator/ServiceFnGenerator.ts +117 -265
  66. package/src/index.ts +4 -4
  67. package/dist/cjs/generator/ServiceFnPromiseGenerator.js +0 -183
  68. package/dist/cjs/generator/ServiceFnPromiseGenerator.js.map +0 -1
  69. package/dist/dts/generator/ServiceFnPromiseGenerator.d.ts +0 -40
  70. package/dist/dts/generator/ServiceFnPromiseGenerator.d.ts.map +0 -1
  71. package/dist/esm/generator/ServiceFnPromiseGenerator.js +0 -175
  72. package/dist/esm/generator/ServiceFnPromiseGenerator.js.map +0 -1
  73. package/generator/ServiceFnPromiseGenerator/package.json +0 -6
  74. package/src/generator/ServiceFnPromiseGenerator.ts +0 -243
@@ -0,0 +1,507 @@
1
+ /**
2
+ * Generator for OData operations (FunctionImports, Functions, Actions).
3
+ *
4
+ * OData V2: FunctionImports
5
+ * OData V4: Functions (GET, no side effects) and Actions (POST, with side effects)
6
+ *
7
+ * @since 1.0.0
8
+ */
9
+ import type { DataModel, OperationModel } from "../model/DataModel.js"
10
+ import type { ODataVersion } from "../parser/EdmxSchema.js"
11
+ import { toCamelCase, toPascalCase } from "./NamingHelper.js"
12
+
13
+ /**
14
+ * Version-specific imports and identifiers.
15
+ */
16
+ interface VersionConfig {
17
+ readonly clientConfigTag: string
18
+ readonly clientConfigImport: string
19
+ readonly clientModule: string
20
+ }
21
+
22
+ const V2_CONFIG: VersionConfig = {
23
+ clientConfigTag: "ODataClientConfig",
24
+ clientConfigImport: "ODataClientConfig",
25
+ clientModule: "ODataClient"
26
+ }
27
+
28
+ const V4_CONFIG: VersionConfig = {
29
+ clientConfigTag: "ODataV4ClientConfig",
30
+ clientConfigImport: "ODataV4ClientConfig",
31
+ clientModule: "ODataV4Client"
32
+ }
33
+
34
+ const getVersionConfig = (version: ODataVersion): VersionConfig => version === "V4" ? V4_CONFIG : V2_CONFIG
35
+
36
+ /**
37
+ * Get the function name for an operation.
38
+ *
39
+ * @since 1.0.0
40
+ * @category naming
41
+ */
42
+ export const getOperationFunctionName = (operation: OperationModel): string => {
43
+ return toCamelCase(operation.name)
44
+ }
45
+
46
+ /**
47
+ * Get the operations module name.
48
+ *
49
+ * @since 1.0.0
50
+ * @category naming
51
+ */
52
+ export const getOperationsModuleName = (): string => "Operations"
53
+
54
+ /**
55
+ * Generated operations file.
56
+ *
57
+ * @since 1.0.0
58
+ * @category types
59
+ */
60
+ export interface GeneratedOperationsFile {
61
+ readonly fileName: string
62
+ readonly content: string
63
+ }
64
+
65
+ /**
66
+ * Result of operations generation.
67
+ *
68
+ * @since 1.0.0
69
+ * @category types
70
+ */
71
+ export interface OperationsGenerationResult {
72
+ readonly operationsFile: GeneratedOperationsFile | null
73
+ }
74
+
75
+ /**
76
+ * Generate the operations file.
77
+ *
78
+ * @since 1.0.0
79
+ * @category generation
80
+ */
81
+ export const generateOperations = (dataModel: DataModel): OperationsGenerationResult => {
82
+ // Get unbound operations only (bound operations are attached to entity services)
83
+ const unboundOperations = Array.from(dataModel.operations.values()).filter((op) => !op.isBound)
84
+
85
+ if (unboundOperations.length === 0) {
86
+ return { operationsFile: null }
87
+ }
88
+
89
+ const content = generateOperationsFile(unboundOperations, dataModel)
90
+
91
+ return {
92
+ operationsFile: {
93
+ fileName: `${getOperationsModuleName()}.ts`,
94
+ content
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Determine if an operation returns an entity or complex type that needs a schema.
101
+ */
102
+ const returnsModelType = (operation: OperationModel, dataModel: DataModel): boolean => {
103
+ if (!operation.returnType) return false
104
+
105
+ const { odataType } = operation.returnType
106
+ // Remove Collection() wrapper if present
107
+ const baseType = odataType.replace(/^Collection\(|\)$/g, "")
108
+ // Get the type name (may be fully qualified)
109
+ const typeName = baseType.includes(".") ? baseType.split(".").pop() ?? baseType : baseType
110
+
111
+ // Check if it's an entity or complex type
112
+ for (const [fqName] of dataModel.entityTypes) {
113
+ if (fqName.endsWith(`.${typeName}`) || fqName === typeName) return true
114
+ }
115
+ for (const [fqName] of dataModel.complexTypes) {
116
+ if (fqName.endsWith(`.${typeName}`) || fqName === typeName) return true
117
+ }
118
+
119
+ return false
120
+ }
121
+
122
+ /**
123
+ * Get the return type's TypeScript type name.
124
+ */
125
+ const getReturnTypeName = (operation: OperationModel): string => {
126
+ if (!operation.returnType) return "void"
127
+
128
+ const { isCollection, typeMapping } = operation.returnType
129
+ const baseType = typeMapping.tsType
130
+
131
+ return isCollection ? `ReadonlyArray<${baseType}>` : baseType
132
+ }
133
+
134
+ /**
135
+ * Collect all model types that need to be imported.
136
+ */
137
+ const collectModelImports = (operations: ReadonlyArray<OperationModel>, dataModel: DataModel): Set<string> => {
138
+ const imports = new Set<string>()
139
+
140
+ const isModelType = (typeName: string): boolean => {
141
+ for (const [fqName] of dataModel.entityTypes) {
142
+ if (fqName.endsWith(`.${typeName}`) || fqName === typeName) return true
143
+ }
144
+ for (const [fqName] of dataModel.complexTypes) {
145
+ if (fqName.endsWith(`.${typeName}`) || fqName === typeName) return true
146
+ }
147
+ return false
148
+ }
149
+
150
+ for (const operation of operations) {
151
+ // Check return type
152
+ if (operation.returnType) {
153
+ const typeName = operation.returnType.typeMapping.tsType
154
+ if (isModelType(typeName)) {
155
+ imports.add(typeName)
156
+ }
157
+ }
158
+
159
+ // Check parameter types
160
+ for (const param of operation.parameters) {
161
+ const typeName = param.typeMapping.tsType
162
+ if (isModelType(typeName)) {
163
+ imports.add(typeName)
164
+ }
165
+ }
166
+ }
167
+
168
+ return imports
169
+ }
170
+
171
+ /**
172
+ * Generate the operations file content.
173
+ */
174
+ const generateOperationsFile = (
175
+ operations: ReadonlyArray<OperationModel>,
176
+ dataModel: DataModel
177
+ ): string => {
178
+ const lines: Array<string> = []
179
+ const versionConfig = getVersionConfig(dataModel.version)
180
+ const isV4 = dataModel.version === "V4"
181
+
182
+ // Header and imports
183
+ lines.push(`/**`)
184
+ lines.push(` * OData operations for ${dataModel.serviceName} OData ${dataModel.version}.`)
185
+ lines.push(` * Generated by odata-effect-gen.`)
186
+ lines.push(` *`)
187
+ lines.push(` * @since 1.0.0`)
188
+ lines.push(` */`)
189
+ lines.push(`import * as Effect from "effect/Effect"`)
190
+ lines.push(`import { HttpClient } from "@effect/platform"`)
191
+ lines.push(`import type * as HttpClientError from "@effect/platform/HttpClientError"`)
192
+ lines.push(``)
193
+
194
+ // Import Operations module
195
+ lines.push(`import * as ODataOps from "@odata-effect/odata-effect/Operations"`)
196
+ lines.push(`import {`)
197
+ lines.push(` ${versionConfig.clientConfigImport}`)
198
+ lines.push(`} from "@odata-effect/odata-effect/${versionConfig.clientModule}"`)
199
+ lines.push(`import {`)
200
+ if (!isV4) {
201
+ lines.push(` type SapError,`)
202
+ }
203
+ lines.push(` type ODataError,`)
204
+ lines.push(` type ParseError`)
205
+ lines.push(`} from "@odata-effect/odata-effect/Errors"`)
206
+ lines.push(``)
207
+
208
+ // Import model types if needed
209
+ const modelImports = collectModelImports(operations, dataModel)
210
+ if (modelImports.size > 0) {
211
+ lines.push(`import {`)
212
+ const importsList = Array.from(modelImports)
213
+ for (let i = 0; i < importsList.length; i++) {
214
+ const isLast = i === importsList.length - 1
215
+ lines.push(` ${importsList[i]}${isLast ? "" : ","}`)
216
+ }
217
+ lines.push(`} from "./Models"`)
218
+ lines.push(``)
219
+ }
220
+
221
+ // Error type
222
+ lines.push(`// ============================================================================`)
223
+ lines.push(`// Types`)
224
+ lines.push(`// ============================================================================`)
225
+ lines.push(``)
226
+ lines.push(`/**`)
227
+ lines.push(` * Error type for operation calls.`)
228
+ lines.push(` *`)
229
+ lines.push(` * @since 1.0.0`)
230
+ lines.push(` * @category errors`)
231
+ lines.push(` */`)
232
+ lines.push(`export type OperationsError =`)
233
+ lines.push(` | HttpClientError.HttpClientError`)
234
+ lines.push(` | ParseError`)
235
+ if (!isV4) {
236
+ lines.push(` | SapError`)
237
+ }
238
+ lines.push(` | ODataError`)
239
+ lines.push(``)
240
+
241
+ // Dependencies type
242
+ lines.push(`/**`)
243
+ lines.push(` * Dependencies required for operation calls.`)
244
+ lines.push(` *`)
245
+ lines.push(` * @since 1.0.0`)
246
+ lines.push(` * @category context`)
247
+ lines.push(` */`)
248
+ lines.push(`export type OperationsContext = ${versionConfig.clientConfigTag} | HttpClient.HttpClient`)
249
+ lines.push(``)
250
+
251
+ // Generate each operation
252
+ lines.push(`// ============================================================================`)
253
+ lines.push(`// Operations`)
254
+ lines.push(`// ============================================================================`)
255
+ lines.push(``)
256
+
257
+ for (const operation of operations) {
258
+ if (isV4) {
259
+ generateV4Operation(lines, operation, dataModel)
260
+ } else {
261
+ generateV2FunctionImport(lines, operation, dataModel)
262
+ }
263
+ lines.push(``)
264
+ }
265
+
266
+ return lines.join("\n")
267
+ }
268
+
269
+ /**
270
+ * Generate parameter interface for an operation.
271
+ */
272
+ const generateParameterType = (
273
+ operation: OperationModel
274
+ ): string | null => {
275
+ if (operation.parameters.length === 0) return null
276
+
277
+ const typeName = `${toPascalCase(operation.name)}Params`
278
+ return typeName
279
+ }
280
+
281
+ /**
282
+ * Generate parameter interface definition.
283
+ */
284
+ const generateParameterInterface = (
285
+ lines: Array<string>,
286
+ operation: OperationModel
287
+ ): void => {
288
+ if (operation.parameters.length === 0) return
289
+
290
+ const typeName = `${toPascalCase(operation.name)}Params`
291
+
292
+ lines.push(`/**`)
293
+ lines.push(` * Parameters for ${operation.odataName} operation.`)
294
+ lines.push(` *`)
295
+ lines.push(` * @since 1.0.0`)
296
+ lines.push(` * @category params`)
297
+ lines.push(` */`)
298
+ lines.push(`export interface ${typeName} {`)
299
+
300
+ for (const param of operation.parameters) {
301
+ const optionalMark = param.isNullable ? "?" : ""
302
+ const tsType = param.isCollection
303
+ ? `ReadonlyArray<${param.typeMapping.tsType}>`
304
+ : param.typeMapping.tsType
305
+ lines.push(` readonly ${param.name}${optionalMark}: ${tsType}`)
306
+ }
307
+
308
+ lines.push(`}`)
309
+ lines.push(``)
310
+ }
311
+
312
+ /**
313
+ * Generate V2 FunctionImport function.
314
+ */
315
+ const generateV2FunctionImport = (
316
+ lines: Array<string>,
317
+ operation: OperationModel,
318
+ dataModel: DataModel
319
+ ): void => {
320
+ const fnName = getOperationFunctionName(operation)
321
+ const paramsType = generateParameterType(operation)
322
+ const returnType = getReturnTypeName(operation)
323
+ const returnsModel = returnsModelType(operation, dataModel)
324
+
325
+ // Generate parameter interface if needed
326
+ generateParameterInterface(lines, operation)
327
+
328
+ lines.push(`/**`)
329
+ lines.push(` * Execute the ${operation.odataName} function import.`)
330
+ lines.push(` *`)
331
+ lines.push(` * @since 1.0.0`)
332
+ lines.push(` * @category operations`)
333
+ lines.push(` */`)
334
+
335
+ // Function signature
336
+ if (paramsType) {
337
+ lines.push(`export const ${fnName} = (`)
338
+ lines.push(` params: ${paramsType}`)
339
+ lines.push(`): Effect.Effect<`)
340
+ } else {
341
+ lines.push(`export const ${fnName} = (): Effect.Effect<`)
342
+ }
343
+
344
+ lines.push(` ${returnType},`)
345
+ lines.push(` OperationsError,`)
346
+ lines.push(` OperationsContext`)
347
+ lines.push(`> =>`)
348
+ lines.push(` Effect.gen(function*() {`)
349
+ lines.push(` const config = yield* ${V2_CONFIG.clientConfigImport}`)
350
+ lines.push(` const client = yield* HttpClient.HttpClient`)
351
+ lines.push(``)
352
+
353
+ // Build parameters object for the function import URL
354
+ if (paramsType) {
355
+ lines.push(` const parameters: ODataOps.OperationParameters = {`)
356
+ for (const param of operation.parameters) {
357
+ lines.push(` ${param.name}: params.${param.name},`)
358
+ }
359
+ lines.push(` }`)
360
+ lines.push(``)
361
+ }
362
+
363
+ const paramsArg = paramsType ? "parameters" : "undefined"
364
+
365
+ // Determine the execute function to use
366
+ if (!operation.returnType) {
367
+ lines.push(
368
+ ` return yield* ODataOps.executeFunctionImportVoid(client, config, "${operation.odataName}", ${paramsArg})`
369
+ )
370
+ } else if (operation.returnType.isCollection) {
371
+ if (returnsModel) {
372
+ lines.push(
373
+ ` return yield* ODataOps.executeFunctionImportCollection(client, config, "${operation.odataName}", ${operation.returnType.typeMapping.tsType}, ${paramsArg})`
374
+ )
375
+ } else {
376
+ lines.push(
377
+ ` return yield* ODataOps.executeFunctionImportCollection(client, config, "${operation.odataName}", ${operation.returnType.typeMapping.effectSchema}, ${paramsArg})`
378
+ )
379
+ }
380
+ } else if (returnsModel) {
381
+ lines.push(
382
+ ` return yield* ODataOps.executeFunctionImportEntity(client, config, "${operation.odataName}", ${operation.returnType.typeMapping.tsType}, ${paramsArg})`
383
+ )
384
+ } else {
385
+ // Primitive return
386
+ const propertyName = operation.odataName
387
+ lines.push(
388
+ ` return yield* ODataOps.executeFunctionImportPrimitive(client, config, "${operation.odataName}", "${propertyName}", ${operation.returnType.typeMapping.effectSchema}, ${paramsArg})`
389
+ )
390
+ }
391
+
392
+ lines.push(` })`)
393
+ }
394
+
395
+ /**
396
+ * Generate V4 Function or Action.
397
+ */
398
+ const generateV4Operation = (
399
+ lines: Array<string>,
400
+ operation: OperationModel,
401
+ dataModel: DataModel
402
+ ): void => {
403
+ const fnName = getOperationFunctionName(operation)
404
+ const paramsType = generateParameterType(operation)
405
+ const returnType = getReturnTypeName(operation)
406
+ const returnsModel = returnsModelType(operation, dataModel)
407
+ const isAction = operation.type === "Action"
408
+
409
+ // Generate parameter interface if needed
410
+ generateParameterInterface(lines, operation)
411
+
412
+ lines.push(`/**`)
413
+ lines.push(` * Execute the ${operation.odataName} ${operation.type.toLowerCase()}.`)
414
+ lines.push(` *`)
415
+ lines.push(` * @since 1.0.0`)
416
+ lines.push(` * @category operations`)
417
+ lines.push(` */`)
418
+
419
+ // Function signature
420
+ if (paramsType) {
421
+ lines.push(`export const ${fnName} = (`)
422
+ lines.push(` params: ${paramsType}`)
423
+ lines.push(`): Effect.Effect<`)
424
+ } else {
425
+ lines.push(`export const ${fnName} = (): Effect.Effect<`)
426
+ }
427
+
428
+ lines.push(` ${returnType},`)
429
+ lines.push(` OperationsError,`)
430
+ lines.push(` OperationsContext`)
431
+ lines.push(`> =>`)
432
+ lines.push(` Effect.gen(function*() {`)
433
+ lines.push(` const config = yield* ${V4_CONFIG.clientConfigImport}`)
434
+ lines.push(` const client = yield* HttpClient.HttpClient`)
435
+ lines.push(``)
436
+
437
+ // Build URL for functions or action
438
+ if (!isAction) {
439
+ // V4 Functions use GET with parameters in URL
440
+ if (paramsType) {
441
+ lines.push(` const parameters: ODataOps.OperationParameters = {`)
442
+ for (const param of operation.parameters) {
443
+ lines.push(` ${param.name}: params.${param.name},`)
444
+ }
445
+ lines.push(` }`)
446
+ lines.push(` const url = ODataOps.buildV4FunctionUrl("${operation.odataName}", parameters)`)
447
+ } else {
448
+ lines.push(` const url = ODataOps.buildV4FunctionUrl("${operation.odataName}")`)
449
+ }
450
+ lines.push(``)
451
+
452
+ // Determine the execute function to use
453
+ if (!operation.returnType) {
454
+ lines.push(` return yield* ODataOps.executeV4FunctionVoid(client, config, url)`)
455
+ } else if (operation.returnType.isCollection) {
456
+ if (returnsModel) {
457
+ lines.push(
458
+ ` return yield* ODataOps.executeV4FunctionCollection(client, config, url, ${operation.returnType.typeMapping.tsType})`
459
+ )
460
+ } else {
461
+ lines.push(
462
+ ` return yield* ODataOps.executeV4FunctionCollection(client, config, url, ${operation.returnType.typeMapping.effectSchema})`
463
+ )
464
+ }
465
+ } else if (returnsModel) {
466
+ lines.push(
467
+ ` return yield* ODataOps.executeV4FunctionEntity(client, config, url, ${operation.returnType.typeMapping.tsType})`
468
+ )
469
+ } else {
470
+ // Primitive return
471
+ lines.push(
472
+ ` return yield* ODataOps.executeV4FunctionPrimitive(client, config, url, ${operation.returnType.typeMapping.effectSchema})`
473
+ )
474
+ }
475
+ } else {
476
+ // V4 Actions use POST with body
477
+ lines.push(` const url = "${operation.odataName}"`)
478
+ lines.push(``)
479
+
480
+ const bodyArg = paramsType ? "params" : "undefined"
481
+
482
+ if (!operation.returnType) {
483
+ lines.push(` return yield* ODataOps.executeV4ActionVoid(client, config, url, ${bodyArg})`)
484
+ } else if (operation.returnType.isCollection) {
485
+ if (returnsModel) {
486
+ lines.push(
487
+ ` return yield* ODataOps.executeV4ActionCollection(client, config, url, ${operation.returnType.typeMapping.tsType}, ${bodyArg})`
488
+ )
489
+ } else {
490
+ lines.push(
491
+ ` return yield* ODataOps.executeV4ActionCollection(client, config, url, ${operation.returnType.typeMapping.effectSchema}, ${bodyArg})`
492
+ )
493
+ }
494
+ } else if (returnsModel) {
495
+ lines.push(
496
+ ` return yield* ODataOps.executeV4ActionEntity(client, config, url, ${operation.returnType.typeMapping.tsType}, ${bodyArg})`
497
+ )
498
+ } else {
499
+ // Primitive return - use entity with schema
500
+ lines.push(
501
+ ` return yield* ODataOps.executeV4FunctionPrimitive(client, config, url, ${operation.returnType.typeMapping.effectSchema})`
502
+ )
503
+ }
504
+ }
505
+
506
+ lines.push(` })`)
507
+ }
@@ -35,7 +35,7 @@ export const generateQueryModels = (dataModel: DataModel): string => {
35
35
  lines.push(` ${Array.from(queryPathTypes).join(",\n ")},`)
36
36
  lines.push(` createQueryBuilder,`)
37
37
  lines.push(` type QueryBuilder`)
38
- lines.push(`} from "@odata-effect/odata-effect"`)
38
+ lines.push(`} from "@odata-effect/odata-effect/QueryBuilder"`)
39
39
 
40
40
  // Import entity types that have entity sets (for QueryBuilder generics)
41
41
  const usedEntityNames: Array<string> = []
@@ -48,7 +48,7 @@ export const generateQueryModels = (dataModel: DataModel): string => {
48
48
  if (usedEntityNames.length > 0) {
49
49
  lines.push(`import type {`)
50
50
  lines.push(` ${usedEntityNames.join(",\n ")}`)
51
- lines.push(`} from "./Models.js"`)
51
+ lines.push(`} from "./Models"`)
52
52
  }
53
53
  lines.push(``)
54
54