@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.
- package/README.md +232 -5
- package/dist/cjs/Cli.js +5 -1
- package/dist/cjs/Cli.js.map +1 -1
- package/dist/cjs/generator/Generator.js +35 -25
- package/dist/cjs/generator/Generator.js.map +1 -1
- package/dist/cjs/generator/IndexGenerator.js +29 -80
- package/dist/cjs/generator/IndexGenerator.js.map +1 -1
- package/dist/cjs/generator/ModelsGenerator.js +2 -5
- package/dist/cjs/generator/ModelsGenerator.js.map +1 -1
- package/dist/cjs/generator/NamingHelper.js +4 -1
- package/dist/cjs/generator/NamingHelper.js.map +1 -1
- package/dist/cjs/generator/NavigationGenerator.js +370 -0
- package/dist/cjs/generator/NavigationGenerator.js.map +1 -0
- package/dist/cjs/generator/OperationsGenerator.js +384 -0
- package/dist/cjs/generator/OperationsGenerator.js.map +1 -0
- package/dist/cjs/generator/QueryModelsGenerator.js +2 -2
- package/dist/cjs/generator/QueryModelsGenerator.js.map +1 -1
- package/dist/cjs/generator/ServiceFnGenerator.js +102 -224
- package/dist/cjs/generator/ServiceFnGenerator.js.map +1 -1
- package/dist/cjs/index.js +3 -3
- package/dist/dts/Cli.d.ts.map +1 -1
- package/dist/dts/generator/Generator.d.ts +2 -0
- package/dist/dts/generator/Generator.d.ts.map +1 -1
- package/dist/dts/generator/IndexGenerator.d.ts.map +1 -1
- package/dist/dts/generator/NamingHelper.d.ts.map +1 -1
- package/dist/dts/generator/NavigationGenerator.d.ts +55 -0
- package/dist/dts/generator/NavigationGenerator.d.ts.map +1 -0
- package/dist/dts/generator/OperationsGenerator.d.ts +50 -0
- package/dist/dts/generator/OperationsGenerator.d.ts.map +1 -0
- package/dist/dts/generator/ServiceFnGenerator.d.ts +10 -11
- package/dist/dts/generator/ServiceFnGenerator.d.ts.map +1 -1
- package/dist/dts/index.d.ts +4 -4
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/Cli.js +5 -1
- package/dist/esm/Cli.js.map +1 -1
- package/dist/esm/generator/Generator.js +35 -25
- package/dist/esm/generator/Generator.js.map +1 -1
- package/dist/esm/generator/IndexGenerator.js +29 -80
- package/dist/esm/generator/IndexGenerator.js.map +1 -1
- package/dist/esm/generator/ModelsGenerator.js +2 -5
- package/dist/esm/generator/ModelsGenerator.js.map +1 -1
- package/dist/esm/generator/NamingHelper.js +4 -1
- package/dist/esm/generator/NamingHelper.js.map +1 -1
- package/dist/esm/generator/NavigationGenerator.js +362 -0
- package/dist/esm/generator/NavigationGenerator.js.map +1 -0
- package/dist/esm/generator/OperationsGenerator.js +375 -0
- package/dist/esm/generator/OperationsGenerator.js.map +1 -0
- package/dist/esm/generator/QueryModelsGenerator.js +2 -2
- package/dist/esm/generator/QueryModelsGenerator.js.map +1 -1
- package/dist/esm/generator/ServiceFnGenerator.js +102 -224
- package/dist/esm/generator/ServiceFnGenerator.js.map +1 -1
- package/dist/esm/index.js +4 -4
- package/dist/esm/index.js.map +1 -1
- package/generator/NavigationGenerator/package.json +6 -0
- package/generator/OperationsGenerator/package.json +6 -0
- package/package.json +17 -9
- package/src/Cli.ts +15 -2
- package/src/generator/Generator.ts +44 -23
- package/src/generator/IndexGenerator.ts +29 -80
- package/src/generator/ModelsGenerator.ts +2 -5
- package/src/generator/NamingHelper.ts +8 -1
- package/src/generator/NavigationGenerator.ts +485 -0
- package/src/generator/OperationsGenerator.ts +507 -0
- package/src/generator/QueryModelsGenerator.ts +2 -2
- package/src/generator/ServiceFnGenerator.ts +117 -265
- package/src/index.ts +4 -4
- package/dist/cjs/generator/ServiceFnPromiseGenerator.js +0 -183
- package/dist/cjs/generator/ServiceFnPromiseGenerator.js.map +0 -1
- package/dist/dts/generator/ServiceFnPromiseGenerator.d.ts +0 -40
- package/dist/dts/generator/ServiceFnPromiseGenerator.d.ts.map +0 -1
- package/dist/esm/generator/ServiceFnPromiseGenerator.js +0 -175
- package/dist/esm/generator/ServiceFnPromiseGenerator.js.map +0 -1
- package/generator/ServiceFnPromiseGenerator/package.json +0 -6
- 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
|
|
51
|
+
lines.push(`} from "./Models"`)
|
|
52
52
|
}
|
|
53
53
|
lines.push(``)
|
|
54
54
|
|