@odata-effect/odata-effect-generator 0.3.1 → 0.4.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/dist/cjs/Cli.js +19 -3
- package/dist/cjs/Cli.js.map +1 -1
- package/dist/cjs/digester/Digester.js +34 -30
- package/dist/cjs/digester/Digester.js.map +1 -1
- package/dist/cjs/generator/Generator.js.map +1 -1
- package/dist/cjs/generator/ModelsGenerator.js +26 -2
- package/dist/cjs/generator/ModelsGenerator.js.map +1 -1
- package/dist/cjs/generator/NamingHelper.js +90 -1
- package/dist/cjs/generator/NamingHelper.js.map +1 -1
- package/dist/cjs/generator/OperationsGenerator.js +44 -16
- package/dist/cjs/generator/OperationsGenerator.js.map +1 -1
- package/dist/cjs/index.js +7 -3
- package/dist/cjs/model/DataModel.js.map +1 -1
- package/dist/cjs/model/GeneratorConfig.js +70 -0
- package/dist/cjs/model/GeneratorConfig.js.map +1 -0
- package/dist/dts/Cli.d.ts.map +1 -1
- package/dist/dts/digester/Digester.d.ts +2 -1
- package/dist/dts/digester/Digester.d.ts.map +1 -1
- package/dist/dts/generator/Generator.d.ts +7 -4
- package/dist/dts/generator/Generator.d.ts.map +1 -1
- package/dist/dts/generator/NamingHelper.d.ts +49 -0
- package/dist/dts/generator/NamingHelper.d.ts.map +1 -1
- package/dist/dts/generator/OperationsGenerator.d.ts +1 -0
- package/dist/dts/generator/OperationsGenerator.d.ts.map +1 -1
- package/dist/dts/index.d.ts +41 -12
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/model/DataModel.d.ts +1 -0
- package/dist/dts/model/DataModel.d.ts.map +1 -1
- package/dist/dts/model/GeneratorConfig.d.ts +114 -0
- package/dist/dts/model/GeneratorConfig.d.ts.map +1 -0
- package/dist/esm/Cli.js +19 -3
- package/dist/esm/Cli.js.map +1 -1
- package/dist/esm/digester/Digester.js +35 -31
- package/dist/esm/digester/Digester.js.map +1 -1
- package/dist/esm/generator/Generator.js.map +1 -1
- package/dist/esm/generator/ModelsGenerator.js +26 -2
- package/dist/esm/generator/ModelsGenerator.js.map +1 -1
- package/dist/esm/generator/NamingHelper.js +84 -0
- package/dist/esm/generator/NamingHelper.js.map +1 -1
- package/dist/esm/generator/OperationsGenerator.js +45 -17
- package/dist/esm/generator/OperationsGenerator.js.map +1 -1
- package/dist/esm/index.js +41 -12
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/model/DataModel.js.map +1 -1
- package/dist/esm/model/GeneratorConfig.js +60 -0
- package/dist/esm/model/GeneratorConfig.js.map +1 -0
- package/model/GeneratorConfig/package.json +6 -0
- package/package.json +9 -1
- package/src/Cli.ts +26 -3
- package/src/digester/Digester.ts +55 -29
- package/src/generator/Generator.ts +7 -4
- package/src/generator/ModelsGenerator.ts +31 -2
- package/src/generator/NamingHelper.ts +115 -0
- package/src/generator/OperationsGenerator.ts +51 -18
- package/src/index.ts +43 -12
- package/src/model/DataModel.ts +1 -0
- package/src/model/GeneratorConfig.ts +180 -0
package/src/digester/Digester.ts
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as Effect from "effect/Effect"
|
|
8
8
|
import * as Schema from "effect/Schema"
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getClassName,
|
|
11
|
+
getClassNameWithOverrides,
|
|
12
|
+
getOperationNameWithOverrides,
|
|
13
|
+
getOperationParameterNameWithOverrides,
|
|
14
|
+
getPropertyNameWithOverrides
|
|
15
|
+
} from "../generator/NamingHelper.js"
|
|
16
|
+
import type { NamingOverrides } from "../model/GeneratorConfig.js"
|
|
10
17
|
import {
|
|
11
18
|
type ComplexTypeModel,
|
|
12
19
|
createDataModel,
|
|
@@ -69,6 +76,7 @@ interface DigestContext {
|
|
|
69
76
|
readonly enumTypes: Set<string>
|
|
70
77
|
readonly complexTypes: Set<string>
|
|
71
78
|
readonly entityTypes: Set<string>
|
|
79
|
+
readonly overrides?: NamingOverrides | undefined
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
/**
|
|
@@ -78,7 +86,8 @@ interface DigestContext {
|
|
|
78
86
|
* @category digestion
|
|
79
87
|
*/
|
|
80
88
|
export const digestMetadata = (
|
|
81
|
-
edmx: ODataEdmxModel
|
|
89
|
+
edmx: ODataEdmxModel,
|
|
90
|
+
overrides?: NamingOverrides
|
|
82
91
|
): Effect.Effect<DataModel, DigestError> =>
|
|
83
92
|
Effect.try({
|
|
84
93
|
try: () => {
|
|
@@ -107,7 +116,7 @@ export const digestMetadata = (
|
|
|
107
116
|
const dataModel = createDataModel(version, namespace, serviceName)
|
|
108
117
|
|
|
109
118
|
// Build context for type resolution
|
|
110
|
-
const context = buildContext(version, namespace, schemas)
|
|
119
|
+
const context = buildContext(version, namespace, schemas, overrides)
|
|
111
120
|
|
|
112
121
|
// First pass: collect all type names
|
|
113
122
|
for (const schema of schemas) {
|
|
@@ -139,7 +148,8 @@ export const digestMetadata = (
|
|
|
139
148
|
const buildContext = (
|
|
140
149
|
version: ODataVersion,
|
|
141
150
|
namespace: string,
|
|
142
|
-
schemas: ReadonlyArray<EdmxSchema
|
|
151
|
+
schemas: ReadonlyArray<EdmxSchema>,
|
|
152
|
+
overrides?: NamingOverrides
|
|
143
153
|
): DigestContext => {
|
|
144
154
|
const associations = new Map<string, Association>()
|
|
145
155
|
|
|
@@ -159,7 +169,8 @@ const buildContext = (
|
|
|
159
169
|
associations,
|
|
160
170
|
enumTypes: new Set(),
|
|
161
171
|
complexTypes: new Set(),
|
|
162
|
-
entityTypes: new Set()
|
|
172
|
+
entityTypes: new Set(),
|
|
173
|
+
overrides
|
|
163
174
|
}
|
|
164
175
|
}
|
|
165
176
|
|
|
@@ -265,14 +276,18 @@ const digestComplexType = (
|
|
|
265
276
|
namespace: string,
|
|
266
277
|
context: DigestContext
|
|
267
278
|
): ComplexTypeModel => {
|
|
268
|
-
const
|
|
269
|
-
const properties = (complexType.Property ?? []).map((p) =>
|
|
270
|
-
|
|
279
|
+
const odataTypeName = complexType.$.Name
|
|
280
|
+
const properties = (complexType.Property ?? []).map((p) =>
|
|
281
|
+
digestProperty(p, [], odataTypeName, "complex", context)
|
|
282
|
+
)
|
|
283
|
+
const navigationProperties = (complexType.NavigationProperty ?? []).map((np) =>
|
|
284
|
+
digestNavigationProperty(np, odataTypeName, "complex", context)
|
|
285
|
+
)
|
|
271
286
|
|
|
272
287
|
const result: ComplexTypeModel = {
|
|
273
|
-
fqName: `${namespace}.${
|
|
274
|
-
odataName:
|
|
275
|
-
name:
|
|
288
|
+
fqName: `${namespace}.${odataTypeName}`,
|
|
289
|
+
odataName: odataTypeName,
|
|
290
|
+
name: getClassNameWithOverrides(odataTypeName, "complex", context.overrides),
|
|
276
291
|
properties,
|
|
277
292
|
navigationProperties,
|
|
278
293
|
isAbstract: complexType.$.Abstract === "true",
|
|
@@ -294,7 +309,7 @@ const digestEntityType = (
|
|
|
294
309
|
namespace: string,
|
|
295
310
|
context: DigestContext
|
|
296
311
|
): EntityTypeModel => {
|
|
297
|
-
const
|
|
312
|
+
const odataTypeName = entityType.$.Name
|
|
298
313
|
const keyNames = new Set<string>()
|
|
299
314
|
|
|
300
315
|
// Collect key property names
|
|
@@ -304,15 +319,19 @@ const digestEntityType = (
|
|
|
304
319
|
}
|
|
305
320
|
}
|
|
306
321
|
|
|
307
|
-
const properties = (entityType.Property ?? []).map((p) =>
|
|
322
|
+
const properties = (entityType.Property ?? []).map((p) =>
|
|
323
|
+
digestProperty(p, Array.from(keyNames), odataTypeName, "entity", context)
|
|
324
|
+
)
|
|
308
325
|
|
|
309
326
|
const keys = properties.filter((p) => p.isKey)
|
|
310
|
-
const navigationProperties = (entityType.NavigationProperty ?? []).map((np) =>
|
|
327
|
+
const navigationProperties = (entityType.NavigationProperty ?? []).map((np) =>
|
|
328
|
+
digestNavigationProperty(np, odataTypeName, "entity", context)
|
|
329
|
+
)
|
|
311
330
|
|
|
312
331
|
const result: EntityTypeModel = {
|
|
313
|
-
fqName: `${namespace}.${
|
|
314
|
-
odataName:
|
|
315
|
-
name:
|
|
332
|
+
fqName: `${namespace}.${odataTypeName}`,
|
|
333
|
+
odataName: odataTypeName,
|
|
334
|
+
name: getClassNameWithOverrides(odataTypeName, "entity", context.overrides),
|
|
316
335
|
keys,
|
|
317
336
|
properties,
|
|
318
337
|
navigationProperties,
|
|
@@ -333,6 +352,8 @@ const digestEntityType = (
|
|
|
333
352
|
const digestProperty = (
|
|
334
353
|
property: Property,
|
|
335
354
|
keyNames: ReadonlyArray<string>,
|
|
355
|
+
ownerTypeName: string,
|
|
356
|
+
ownerTypeKind: "entity" | "complex",
|
|
336
357
|
context: DigestContext
|
|
337
358
|
): PropertyModel => {
|
|
338
359
|
const odataName = property.$.Name
|
|
@@ -345,7 +366,7 @@ const digestProperty = (
|
|
|
345
366
|
|
|
346
367
|
const result: PropertyModel = {
|
|
347
368
|
odataName,
|
|
348
|
-
name:
|
|
369
|
+
name: getPropertyNameWithOverrides(odataName, ownerTypeName, ownerTypeKind, context.overrides),
|
|
349
370
|
odataType,
|
|
350
371
|
typeMapping,
|
|
351
372
|
isCollection,
|
|
@@ -380,6 +401,8 @@ const isV4NavigationProperty = (
|
|
|
380
401
|
*/
|
|
381
402
|
const digestNavigationProperty = (
|
|
382
403
|
navProp: NavigationProperty,
|
|
404
|
+
ownerTypeName: string,
|
|
405
|
+
ownerTypeKind: "entity" | "complex",
|
|
383
406
|
context: DigestContext
|
|
384
407
|
): NavigationPropertyModel => {
|
|
385
408
|
const odataName = navProp.$.Name
|
|
@@ -391,7 +414,7 @@ const digestNavigationProperty = (
|
|
|
391
414
|
|
|
392
415
|
const result: NavigationPropertyModel = {
|
|
393
416
|
odataName,
|
|
394
|
-
name:
|
|
417
|
+
name: getPropertyNameWithOverrides(odataName, ownerTypeName, ownerTypeKind, context.overrides),
|
|
395
418
|
targetType: targetTypeName,
|
|
396
419
|
isCollection,
|
|
397
420
|
isNullable: navProp.$.Nullable !== "false"
|
|
@@ -423,7 +446,7 @@ const digestNavigationProperty = (
|
|
|
423
446
|
|
|
424
447
|
return {
|
|
425
448
|
odataName,
|
|
426
|
-
name:
|
|
449
|
+
name: getPropertyNameWithOverrides(odataName, ownerTypeName, ownerTypeKind, context.overrides),
|
|
427
450
|
targetType,
|
|
428
451
|
isCollection,
|
|
429
452
|
isNullable: true
|
|
@@ -467,10 +490,12 @@ const digestEntityContainer = (
|
|
|
467
490
|
for (const funcImport of container.FunctionImport) {
|
|
468
491
|
// Only process V2 function imports (no Function reference)
|
|
469
492
|
if (!funcImport.$.Function) {
|
|
493
|
+
const odataOpName = funcImport.$.Name
|
|
470
494
|
const params: Array<OperationParameterModel> = (funcImport.Parameter ?? []).map((p) => {
|
|
471
495
|
const { baseType, isCollection } = parseODataType(p.$.Type)
|
|
472
496
|
return {
|
|
473
|
-
name:
|
|
497
|
+
name: getOperationParameterNameWithOverrides(p.$.Name, odataOpName, context.overrides),
|
|
498
|
+
odataName: p.$.Name,
|
|
474
499
|
odataType: p.$.Type,
|
|
475
500
|
typeMapping: resolveTypeMapping(baseType, context),
|
|
476
501
|
isNullable: p.$.Nullable !== "false",
|
|
@@ -479,9 +504,9 @@ const digestEntityContainer = (
|
|
|
479
504
|
})
|
|
480
505
|
|
|
481
506
|
let model: OperationModel = {
|
|
482
|
-
fqName: `${context.namespace}.${
|
|
483
|
-
odataName:
|
|
484
|
-
name:
|
|
507
|
+
fqName: `${context.namespace}.${odataOpName}`,
|
|
508
|
+
odataName: odataOpName,
|
|
509
|
+
name: getOperationNameWithOverrides(odataOpName, context.overrides),
|
|
485
510
|
type: "Function",
|
|
486
511
|
isBound: false,
|
|
487
512
|
parameters: params
|
|
@@ -519,13 +544,14 @@ const digestOperation = (
|
|
|
519
544
|
type: "Function" | "Action",
|
|
520
545
|
context: DigestContext
|
|
521
546
|
): OperationModel => {
|
|
522
|
-
const
|
|
547
|
+
const odataName = operation.$.Name
|
|
523
548
|
const isBound = operation.$.IsBound === "true"
|
|
524
549
|
|
|
525
550
|
const allParams = (operation.Parameter ?? []).map((p) => {
|
|
526
551
|
const { baseType, isCollection } = parseODataType(p.$.Type)
|
|
527
552
|
return {
|
|
528
|
-
name:
|
|
553
|
+
name: getOperationParameterNameWithOverrides(p.$.Name, odataName, context.overrides),
|
|
554
|
+
odataName: p.$.Name,
|
|
529
555
|
odataType: p.$.Type,
|
|
530
556
|
typeMapping: resolveTypeMapping(baseType, context),
|
|
531
557
|
isNullable: p.$.Nullable !== "false",
|
|
@@ -538,9 +564,9 @@ const digestOperation = (
|
|
|
538
564
|
const parameters = isBound ? allParams.slice(1) : allParams
|
|
539
565
|
|
|
540
566
|
let model: OperationModel = {
|
|
541
|
-
fqName: `${namespace}.${
|
|
542
|
-
odataName
|
|
543
|
-
name:
|
|
567
|
+
fqName: `${namespace}.${odataName}`,
|
|
568
|
+
odataName,
|
|
569
|
+
name: getOperationNameWithOverrides(odataName, context.overrides),
|
|
544
570
|
type,
|
|
545
571
|
isBound,
|
|
546
572
|
parameters
|
|
@@ -8,6 +8,7 @@ import * as Path from "@effect/platform/Path"
|
|
|
8
8
|
import * as Effect from "effect/Effect"
|
|
9
9
|
import * as Schema from "effect/Schema"
|
|
10
10
|
import type { DataModel } from "../model/DataModel.js"
|
|
11
|
+
import type { NamingOverrides } from "../model/GeneratorConfig.js"
|
|
11
12
|
import { generateIndex } from "./IndexGenerator.js"
|
|
12
13
|
import { generateModels } from "./ModelsGenerator.js"
|
|
13
14
|
import { generateNavigations } from "./NavigationGenerator.js"
|
|
@@ -32,11 +33,13 @@ import { generateServiceFns } from "./ServiceFnGenerator.js"
|
|
|
32
33
|
*/
|
|
33
34
|
export interface GeneratorConfig {
|
|
34
35
|
readonly outputDir: string
|
|
35
|
-
readonly packageName?: string
|
|
36
|
-
readonly serviceName?: string
|
|
37
|
-
readonly force?: boolean
|
|
36
|
+
readonly packageName?: string | undefined
|
|
37
|
+
readonly serviceName?: string | undefined
|
|
38
|
+
readonly force?: boolean | undefined
|
|
38
39
|
/** Generate only source files directly in outputDir (no package.json, tsconfig, src/ subdirectory) */
|
|
39
|
-
readonly filesOnly?: boolean
|
|
40
|
+
readonly filesOnly?: boolean | undefined
|
|
41
|
+
/** Optional naming overrides for properties and types */
|
|
42
|
+
readonly overrides?: NamingOverrides | undefined
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
/**
|
|
@@ -291,7 +291,8 @@ const generateSchemaFields = (
|
|
|
291
291
|
const prop = properties[i]
|
|
292
292
|
const schemaType = getPropertySchemaType(prop, prop.isNullable && !prop.isKey)
|
|
293
293
|
const isLast = i === properties.length - 1
|
|
294
|
-
|
|
294
|
+
const fieldDef = getPropertyFieldDefinition(prop, schemaType)
|
|
295
|
+
fields.push(`${fieldDef}${isLast ? "" : ","}`)
|
|
295
296
|
}
|
|
296
297
|
|
|
297
298
|
// Navigation properties are intentionally excluded from Schema.Class
|
|
@@ -312,12 +313,40 @@ const generateEditableSchemaFields = (
|
|
|
312
313
|
const prop = properties[i]
|
|
313
314
|
const schemaType = getPropertySchemaType(prop, prop.isNullable)
|
|
314
315
|
const isLast = i === properties.length - 1
|
|
315
|
-
|
|
316
|
+
const fieldDef = getPropertyFieldDefinition(prop, schemaType)
|
|
317
|
+
fields.push(`${fieldDef}${isLast ? "" : ","}`)
|
|
316
318
|
}
|
|
317
319
|
|
|
318
320
|
return fields
|
|
319
321
|
}
|
|
320
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Get a complete property field definition including fromKey mapping if needed.
|
|
325
|
+
*
|
|
326
|
+
* When the OData property name differs from the TypeScript property name,
|
|
327
|
+
* we use Schema.propertySignature with Schema.fromKey to map between them.
|
|
328
|
+
* This is essential for OData V2 responses which use PascalCase property names.
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* // When odataName == name:
|
|
332
|
+
* name: Schema.String
|
|
333
|
+
*
|
|
334
|
+
* // When odataName ("ID") != name ("id"):
|
|
335
|
+
* id: Schema.propertySignature(Schema.Number).pipe(Schema.fromKey("ID"))
|
|
336
|
+
*/
|
|
337
|
+
const getPropertyFieldDefinition = (
|
|
338
|
+
prop: PropertyModel,
|
|
339
|
+
schemaType: string
|
|
340
|
+
): string => {
|
|
341
|
+
// If OData name matches TypeScript name, use simple format
|
|
342
|
+
if (prop.odataName === prop.name) {
|
|
343
|
+
return `${prop.name}: ${schemaType}`
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Use propertySignature with fromKey to map between encoded (OData) and decoded (TypeScript) names
|
|
347
|
+
return `${prop.name}: Schema.propertySignature(${schemaType}).pipe(Schema.fromKey("${prop.odataName}"))`
|
|
348
|
+
}
|
|
349
|
+
|
|
321
350
|
/**
|
|
322
351
|
* Get the Schema type for a property.
|
|
323
352
|
*/
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @since 1.0.0
|
|
5
5
|
*/
|
|
6
|
+
import {
|
|
7
|
+
getOperationOverride,
|
|
8
|
+
getOperationParameterOverride,
|
|
9
|
+
getPropertyOverride,
|
|
10
|
+
getTypeOverride,
|
|
11
|
+
type NamingOverrides
|
|
12
|
+
} from "../model/GeneratorConfig.js"
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
15
|
* Convert a string to PascalCase.
|
|
@@ -164,3 +171,111 @@ export const getEditableTypeName = (typeName: string): string => {
|
|
|
164
171
|
export const getIdTypeName = (typeName: string): string => {
|
|
165
172
|
return `${toPascalCase(typeName)}Id`
|
|
166
173
|
}
|
|
174
|
+
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// Override-aware naming functions
|
|
177
|
+
// ============================================================================
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get the TypeScript property name for an OData property, with override support.
|
|
181
|
+
*
|
|
182
|
+
* @param odataName - The original OData property name
|
|
183
|
+
* @param typeName - The OData type name this property belongs to
|
|
184
|
+
* @param typeKind - Whether the type is an entity or complex type
|
|
185
|
+
* @param overrides - Optional naming overrides configuration
|
|
186
|
+
* @returns The TypeScript property name
|
|
187
|
+
*
|
|
188
|
+
* @since 1.0.0
|
|
189
|
+
* @category naming
|
|
190
|
+
*/
|
|
191
|
+
export const getPropertyNameWithOverrides = (
|
|
192
|
+
odataName: string,
|
|
193
|
+
typeName: string,
|
|
194
|
+
typeKind: "entity" | "complex",
|
|
195
|
+
overrides?: NamingOverrides
|
|
196
|
+
): string => {
|
|
197
|
+
// Check for override first
|
|
198
|
+
const override = getPropertyOverride(overrides, typeName, typeKind, odataName)
|
|
199
|
+
if (override) {
|
|
200
|
+
return override
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Fall back to default camelCase conversion
|
|
204
|
+
return getPropertyName(odataName)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get the TypeScript class name for an OData type, with override support.
|
|
209
|
+
*
|
|
210
|
+
* @param odataName - The original OData type name
|
|
211
|
+
* @param typeKind - Whether the type is an entity or complex type
|
|
212
|
+
* @param overrides - Optional naming overrides configuration
|
|
213
|
+
* @returns The TypeScript class name
|
|
214
|
+
*
|
|
215
|
+
* @since 1.0.0
|
|
216
|
+
* @category naming
|
|
217
|
+
*/
|
|
218
|
+
export const getClassNameWithOverrides = (
|
|
219
|
+
odataName: string,
|
|
220
|
+
typeKind: "entity" | "complex",
|
|
221
|
+
overrides?: NamingOverrides
|
|
222
|
+
): string => {
|
|
223
|
+
// Check for override first
|
|
224
|
+
const override = getTypeOverride(overrides, odataName, typeKind)
|
|
225
|
+
if (override) {
|
|
226
|
+
return override
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Fall back to default PascalCase conversion
|
|
230
|
+
return getClassName(odataName)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get the TypeScript function name for an OData operation, with override support.
|
|
235
|
+
*
|
|
236
|
+
* @param odataName - The original OData operation name
|
|
237
|
+
* @param overrides - Optional naming overrides configuration
|
|
238
|
+
* @returns The TypeScript function name (camelCase)
|
|
239
|
+
*
|
|
240
|
+
* @since 1.0.0
|
|
241
|
+
* @category naming
|
|
242
|
+
*/
|
|
243
|
+
export const getOperationNameWithOverrides = (
|
|
244
|
+
odataName: string,
|
|
245
|
+
overrides?: NamingOverrides
|
|
246
|
+
): string => {
|
|
247
|
+
// Check for override first
|
|
248
|
+
const override = getOperationOverride(overrides, odataName)
|
|
249
|
+
if (override) {
|
|
250
|
+
return override
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Fall back to default camelCase conversion
|
|
254
|
+
return getPropertyName(odataName)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get the TypeScript parameter name for an OData operation parameter, with override support.
|
|
259
|
+
*
|
|
260
|
+
* @param odataParamName - The original OData parameter name
|
|
261
|
+
* @param odataOperationName - The OData operation this parameter belongs to
|
|
262
|
+
* @param overrides - Optional naming overrides configuration
|
|
263
|
+
* @returns The TypeScript parameter name (camelCase)
|
|
264
|
+
*
|
|
265
|
+
* @since 1.0.0
|
|
266
|
+
* @category naming
|
|
267
|
+
*/
|
|
268
|
+
export const getOperationParameterNameWithOverrides = (
|
|
269
|
+
odataParamName: string,
|
|
270
|
+
odataOperationName: string,
|
|
271
|
+
overrides?: NamingOverrides
|
|
272
|
+
): string => {
|
|
273
|
+
// Check for override first
|
|
274
|
+
const override = getOperationParameterOverride(overrides, odataOperationName, odataParamName)
|
|
275
|
+
if (override) {
|
|
276
|
+
return override
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Fall back to default camelCase conversion
|
|
280
|
+
return getPropertyName(odataParamName)
|
|
281
|
+
}
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @since 1.0.0
|
|
8
8
|
*/
|
|
9
|
-
import type { DataModel, OperationModel } from "../model/DataModel.js"
|
|
9
|
+
import type { DataModel, OperationModel, OperationParameterModel } from "../model/DataModel.js"
|
|
10
10
|
import type { ODataVersion } from "../parser/EdmxSchema.js"
|
|
11
|
-
import {
|
|
11
|
+
import { toPascalCase } from "./NamingHelper.js"
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Version-specific imports and identifiers.
|
|
@@ -22,25 +22,26 @@ interface VersionConfig {
|
|
|
22
22
|
const V2_CONFIG: VersionConfig = {
|
|
23
23
|
clientConfigTag: "ODataClientConfig",
|
|
24
24
|
clientConfigImport: "ODataClientConfig",
|
|
25
|
-
clientModule: "
|
|
25
|
+
clientModule: "OData"
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const V4_CONFIG: VersionConfig = {
|
|
29
29
|
clientConfigTag: "ODataV4ClientConfig",
|
|
30
30
|
clientConfigImport: "ODataV4ClientConfig",
|
|
31
|
-
clientModule: "
|
|
31
|
+
clientModule: "ODataV4"
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const getVersionConfig = (version: ODataVersion): VersionConfig => version === "V4" ? V4_CONFIG : V2_CONFIG
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Get the function name for an operation.
|
|
38
|
+
* Note: operation.name is already properly cased by the Digester.
|
|
38
39
|
*
|
|
39
40
|
* @since 1.0.0
|
|
40
41
|
* @category naming
|
|
41
42
|
*/
|
|
42
43
|
export const getOperationFunctionName = (operation: OperationModel): string => {
|
|
43
|
-
return
|
|
44
|
+
return operation.name
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/**
|
|
@@ -274,7 +275,7 @@ const generateParameterType = (
|
|
|
274
275
|
): string | null => {
|
|
275
276
|
if (operation.parameters.length === 0) return null
|
|
276
277
|
|
|
277
|
-
const typeName = `${toPascalCase(operation.
|
|
278
|
+
const typeName = `${toPascalCase(operation.odataName)}Params`
|
|
278
279
|
return typeName
|
|
279
280
|
}
|
|
280
281
|
|
|
@@ -287,7 +288,7 @@ const generateParameterInterface = (
|
|
|
287
288
|
): void => {
|
|
288
289
|
if (operation.parameters.length === 0) return
|
|
289
290
|
|
|
290
|
-
const typeName = `${toPascalCase(operation.
|
|
291
|
+
const typeName = `${toPascalCase(operation.odataName)}Params`
|
|
291
292
|
|
|
292
293
|
lines.push(`/**`)
|
|
293
294
|
lines.push(` * Parameters for ${operation.odataName} operation.`)
|
|
@@ -309,6 +310,26 @@ const generateParameterInterface = (
|
|
|
309
310
|
lines.push(``)
|
|
310
311
|
}
|
|
311
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Build the parameters object mapping TypeScript names to OData names.
|
|
315
|
+
*/
|
|
316
|
+
const generateParametersObject = (
|
|
317
|
+
lines: Array<string>,
|
|
318
|
+
params: ReadonlyArray<OperationParameterModel>,
|
|
319
|
+
indent: string
|
|
320
|
+
): void => {
|
|
321
|
+
lines.push(`${indent}const parameters: ODataOps.OperationParameters = {`)
|
|
322
|
+
for (const param of params) {
|
|
323
|
+
// Map TypeScript param name to OData param name
|
|
324
|
+
if (param.name === param.odataName) {
|
|
325
|
+
lines.push(`${indent} ${param.name}: params.${param.name},`)
|
|
326
|
+
} else {
|
|
327
|
+
lines.push(`${indent} "${param.odataName}": params.${param.name},`)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
lines.push(`${indent}}`)
|
|
331
|
+
}
|
|
332
|
+
|
|
312
333
|
/**
|
|
313
334
|
* Generate V2 FunctionImport function.
|
|
314
335
|
*/
|
|
@@ -352,11 +373,7 @@ const generateV2FunctionImport = (
|
|
|
352
373
|
|
|
353
374
|
// Build parameters object for the function import URL
|
|
354
375
|
if (paramsType) {
|
|
355
|
-
lines.
|
|
356
|
-
for (const param of operation.parameters) {
|
|
357
|
-
lines.push(` ${param.name}: params.${param.name},`)
|
|
358
|
-
}
|
|
359
|
-
lines.push(` }`)
|
|
376
|
+
generateParametersObject(lines, operation.parameters, " ")
|
|
360
377
|
lines.push(``)
|
|
361
378
|
}
|
|
362
379
|
|
|
@@ -438,11 +455,7 @@ const generateV4Operation = (
|
|
|
438
455
|
if (!isAction) {
|
|
439
456
|
// V4 Functions use GET with parameters in URL
|
|
440
457
|
if (paramsType) {
|
|
441
|
-
lines.
|
|
442
|
-
for (const param of operation.parameters) {
|
|
443
|
-
lines.push(` ${param.name}: params.${param.name},`)
|
|
444
|
-
}
|
|
445
|
-
lines.push(` }`)
|
|
458
|
+
generateParametersObject(lines, operation.parameters, " ")
|
|
446
459
|
lines.push(` const url = ODataOps.buildV4FunctionUrl("${operation.odataName}", parameters)`)
|
|
447
460
|
} else {
|
|
448
461
|
lines.push(` const url = ODataOps.buildV4FunctionUrl("${operation.odataName}")`)
|
|
@@ -477,7 +490,27 @@ const generateV4Operation = (
|
|
|
477
490
|
lines.push(` const url = "${operation.odataName}"`)
|
|
478
491
|
lines.push(``)
|
|
479
492
|
|
|
480
|
-
|
|
493
|
+
// Build body with OData parameter names
|
|
494
|
+
let bodyArg = "undefined"
|
|
495
|
+
if (paramsType) {
|
|
496
|
+
// Check if any parameter names differ
|
|
497
|
+
const hasDifferentNames = operation.parameters.some((p) => p.name !== p.odataName)
|
|
498
|
+
if (hasDifferentNames) {
|
|
499
|
+
lines.push(` const body = {`)
|
|
500
|
+
for (const param of operation.parameters) {
|
|
501
|
+
if (param.name === param.odataName) {
|
|
502
|
+
lines.push(` ${param.name}: params.${param.name},`)
|
|
503
|
+
} else {
|
|
504
|
+
lines.push(` "${param.odataName}": params.${param.name},`)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
lines.push(` }`)
|
|
508
|
+
lines.push(``)
|
|
509
|
+
bodyArg = "body"
|
|
510
|
+
} else {
|
|
511
|
+
bodyArg = "params"
|
|
512
|
+
}
|
|
513
|
+
}
|
|
481
514
|
|
|
482
515
|
if (!operation.returnType) {
|
|
483
516
|
lines.push(` return yield* ODataOps.executeV4ActionVoid(client, config, url, ${bodyArg})`)
|
package/src/index.ts
CHANGED
|
@@ -55,6 +55,41 @@ export * as ModelsGenerator from "./generator/ModelsGenerator.js"
|
|
|
55
55
|
*/
|
|
56
56
|
export * as NamingHelper from "./generator/NamingHelper.js"
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Generator for type-safe, tree-shakable navigation path builders.
|
|
60
|
+
*
|
|
61
|
+
* Generates branded path types and navigation functions that can be composed
|
|
62
|
+
* with pipe() for type-safe OData path construction.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* import { pipe } from "effect"
|
|
67
|
+
* import { People, byKey, trips, planItems, asFlight } from "./PathBuilders"
|
|
68
|
+
*
|
|
69
|
+
* const path = pipe(
|
|
70
|
+
* People,
|
|
71
|
+
* byKey("russellwhyte"),
|
|
72
|
+
* trips,
|
|
73
|
+
* byKey(0),
|
|
74
|
+
* planItems,
|
|
75
|
+
* asFlight
|
|
76
|
+
* )
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @since 1.0.0
|
|
80
|
+
*/
|
|
81
|
+
export * as NavigationGenerator from "./generator/NavigationGenerator.js"
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generator for OData operations (FunctionImports, Functions, Actions).
|
|
85
|
+
*
|
|
86
|
+
* OData V2: FunctionImports
|
|
87
|
+
* OData V4: Functions (GET, no side effects) and Actions (POST, with side effects)
|
|
88
|
+
*
|
|
89
|
+
* @since 1.0.0
|
|
90
|
+
*/
|
|
91
|
+
export * as OperationsGenerator from "./generator/OperationsGenerator.js"
|
|
92
|
+
|
|
58
93
|
/**
|
|
59
94
|
* Generator for package configuration files.
|
|
60
95
|
*
|
|
@@ -70,33 +105,29 @@ export * as PackageGenerator from "./generator/PackageGenerator.js"
|
|
|
70
105
|
export * as QueryModelsGenerator from "./generator/QueryModelsGenerator.js"
|
|
71
106
|
|
|
72
107
|
/**
|
|
73
|
-
* Generator for
|
|
108
|
+
* Generator for entity services using the crud factory.
|
|
74
109
|
*
|
|
75
|
-
* This module generates
|
|
76
|
-
*
|
|
77
|
-
* the tree-shakable OData functions directly.
|
|
110
|
+
* This module generates a single Services.ts file that creates CRUD services
|
|
111
|
+
* for all entity sets using the crud factory from @odata-effect/odata-effect.
|
|
78
112
|
*
|
|
79
113
|
* @since 1.0.0
|
|
80
114
|
*/
|
|
81
115
|
export * as ServiceFnGenerator from "./generator/ServiceFnGenerator.js"
|
|
82
116
|
|
|
83
117
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* This module generates pipe-based navigation functions and the toPromise
|
|
87
|
-
* helper for Promise-based environments.
|
|
118
|
+
* Intermediate representation for OData metadata.
|
|
119
|
+
* This is the central data model that digesters create and generators consume.
|
|
88
120
|
*
|
|
89
121
|
* @since 1.0.0
|
|
90
122
|
*/
|
|
91
|
-
export * as
|
|
123
|
+
export * as DataModel from "./model/DataModel.js"
|
|
92
124
|
|
|
93
125
|
/**
|
|
94
|
-
*
|
|
95
|
-
* This is the central data model that digesters create and generators consume.
|
|
126
|
+
* Configuration for code generation including name overrides.
|
|
96
127
|
*
|
|
97
128
|
* @since 1.0.0
|
|
98
129
|
*/
|
|
99
|
-
export * as
|
|
130
|
+
export * as GeneratorConfig from "./model/GeneratorConfig.js"
|
|
100
131
|
|
|
101
132
|
/**
|
|
102
133
|
* Common EDMX schema interfaces for OData metadata parsing.
|
package/src/model/DataModel.ts
CHANGED