@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,344 @@
1
+ /**
2
+ * Generator for Models.ts - Effect Schema definitions.
3
+ *
4
+ * @since 1.0.0
5
+ */
6
+ import type {
7
+ ComplexTypeModel,
8
+ DataModel,
9
+ EntityTypeModel,
10
+ EnumTypeModel,
11
+ NavigationPropertyModel,
12
+ PropertyModel
13
+ } from "../model/DataModel.js"
14
+ import { getClassName, getEditableTypeName, getIdTypeName } from "./NamingHelper.js"
15
+
16
+ /**
17
+ * Get dependencies for a type (complex types it references via properties or baseType).
18
+ */
19
+ const getTypeDependencies = (
20
+ type: ComplexTypeModel | EntityTypeModel,
21
+ allComplexTypes: Set<string>,
22
+ allEntityTypes: Set<string>
23
+ ): Set<string> => {
24
+ const deps = new Set<string>()
25
+
26
+ // Add base type dependency if it exists
27
+ if (type.baseType) {
28
+ const baseTypeName = getClassName(type.baseType.split(".").pop() ?? type.baseType)
29
+ if (allComplexTypes.has(baseTypeName) || allEntityTypes.has(baseTypeName)) {
30
+ deps.add(baseTypeName)
31
+ }
32
+ }
33
+
34
+ // Add property type dependencies (for complex types referenced in properties)
35
+ for (const prop of type.properties) {
36
+ const propTypeName = prop.typeMapping.effectSchema
37
+ if (allComplexTypes.has(propTypeName)) {
38
+ deps.add(propTypeName)
39
+ }
40
+ }
41
+
42
+ return deps
43
+ }
44
+
45
+ /**
46
+ * Topologically sort types based on their dependencies.
47
+ */
48
+ const sortTypesByDependency = <T extends ComplexTypeModel | EntityTypeModel>(
49
+ types: Array<T>,
50
+ allComplexTypes: Set<string>,
51
+ allEntityTypes: Set<string>
52
+ ): Array<T> => {
53
+ const sorted: Array<T> = []
54
+ const visited = new Set<string>()
55
+ const visiting = new Set<string>()
56
+
57
+ const visit = (type: T) => {
58
+ if (visited.has(type.name)) return
59
+ if (visiting.has(type.name)) {
60
+ // Circular dependency - just add it
61
+ visited.add(type.name)
62
+ sorted.push(type)
63
+ return
64
+ }
65
+
66
+ visiting.add(type.name)
67
+
68
+ const deps = getTypeDependencies(type, allComplexTypes, allEntityTypes)
69
+ for (const depName of deps) {
70
+ const depType = types.find((t) => t.name === depName)
71
+ if (depType && !visited.has(depName)) {
72
+ visit(depType)
73
+ }
74
+ }
75
+
76
+ visiting.delete(type.name)
77
+ visited.add(type.name)
78
+ sorted.push(type)
79
+ }
80
+
81
+ for (const type of types) {
82
+ visit(type)
83
+ }
84
+
85
+ return sorted
86
+ }
87
+
88
+ /**
89
+ * Generate the Models.ts file content.
90
+ *
91
+ * @since 1.0.0
92
+ * @category generation
93
+ */
94
+ export const generateModels = (dataModel: DataModel): string => {
95
+ const lines: Array<string> = []
96
+
97
+ // Collect all type names for dependency resolution
98
+ const allComplexTypes = new Set(Array.from(dataModel.complexTypes.values()).map((t) => t.name))
99
+ const allEntityTypes = new Set(Array.from(dataModel.entityTypes.values()).map((t) => t.name))
100
+
101
+ // Header
102
+ lines.push(`/**`)
103
+ lines.push(` * Effect Schema models for ${dataModel.serviceName} OData service.`)
104
+ lines.push(` * Generated by odata-effect-gen.`)
105
+ lines.push(` *`)
106
+ lines.push(` * @since 1.0.0`)
107
+ lines.push(` */`)
108
+ lines.push(`import * as Schema from "effect/Schema"`)
109
+ lines.push(``)
110
+
111
+ // Generate enum types (no dependencies, always first)
112
+ for (const enumType of dataModel.enumTypes.values()) {
113
+ for (const line of generateEnumType(enumType)) lines.push(line)
114
+ lines.push(``)
115
+ }
116
+
117
+ // Generate complex types in dependency order
118
+ const sortedComplexTypes = sortTypesByDependency(
119
+ Array.from(dataModel.complexTypes.values()),
120
+ allComplexTypes,
121
+ allEntityTypes
122
+ )
123
+ for (const complexType of sortedComplexTypes) {
124
+ for (const line of generateComplexType(complexType, dataModel)) lines.push(line)
125
+ lines.push(``)
126
+ }
127
+
128
+ // Generate entity types in dependency order
129
+ const sortedEntityTypes = sortTypesByDependency(
130
+ Array.from(dataModel.entityTypes.values()),
131
+ allComplexTypes,
132
+ allEntityTypes
133
+ )
134
+ for (const entityType of sortedEntityTypes) {
135
+ for (const line of generateEntityType(entityType, dataModel)) lines.push(line)
136
+ lines.push(``)
137
+ }
138
+
139
+ return lines.join("\n")
140
+ }
141
+
142
+ /**
143
+ * Generate an enum type.
144
+ */
145
+ const generateEnumType = (enumType: EnumTypeModel): Array<string> => {
146
+ const lines: Array<string> = []
147
+ const members = enumType.members.map((m) => `"${m.name}"`).join(", ")
148
+
149
+ lines.push(`/**`)
150
+ lines.push(` * ${enumType.odataName} enum type.`)
151
+ lines.push(` *`)
152
+ lines.push(` * @since 1.0.0`)
153
+ lines.push(` * @category enums`)
154
+ lines.push(` */`)
155
+ lines.push(`export const ${enumType.name} = Schema.Literal(${members})`)
156
+ lines.push(`export type ${enumType.name} = Schema.Schema.Type<typeof ${enumType.name}>`)
157
+
158
+ return lines
159
+ }
160
+
161
+ /**
162
+ * Generate a complex type.
163
+ */
164
+ const generateComplexType = (
165
+ complexType: ComplexTypeModel,
166
+ dataModel: DataModel
167
+ ): Array<string> => {
168
+ const lines: Array<string> = []
169
+
170
+ lines.push(`/**`)
171
+ lines.push(` * ${complexType.odataName} complex type.`)
172
+ lines.push(` *`)
173
+ lines.push(` * @since 1.0.0`)
174
+ lines.push(` * @category models`)
175
+ lines.push(` */`)
176
+
177
+ const fields = generateSchemaFields(complexType.properties, complexType.navigationProperties, dataModel)
178
+
179
+ lines.push(`export class ${complexType.name} extends Schema.Class<${complexType.name}>("${complexType.name}")({`)
180
+ for (const f of fields) lines.push(` ${f}`)
181
+ lines.push(`}) {}`)
182
+
183
+ // Generate editable type
184
+ lines.push(``)
185
+ lines.push(`/**`)
186
+ lines.push(` * Editable ${complexType.odataName} for creating/updating operations.`)
187
+ lines.push(` *`)
188
+ lines.push(` * @since 1.0.0`)
189
+ lines.push(` * @category models`)
190
+ lines.push(` */`)
191
+
192
+ const editableFields = generateEditableSchemaFields(complexType.properties)
193
+ const editableName = getEditableTypeName(complexType.name)
194
+
195
+ lines.push(`export const ${editableName} = Schema.Struct({`)
196
+ for (const f of editableFields) lines.push(` ${f}`)
197
+ lines.push(`})`)
198
+ lines.push(`export type ${editableName} = Schema.Schema.Type<typeof ${editableName}>`)
199
+
200
+ return lines
201
+ }
202
+
203
+ /**
204
+ * Generate an entity type with ID and editable variants.
205
+ */
206
+ const generateEntityType = (
207
+ entityType: EntityTypeModel,
208
+ dataModel: DataModel
209
+ ): Array<string> => {
210
+ const lines: Array<string> = []
211
+
212
+ // Main entity class
213
+ lines.push(`/**`)
214
+ lines.push(` * ${entityType.odataName} entity type.`)
215
+ lines.push(` *`)
216
+ lines.push(` * @since 1.0.0`)
217
+ lines.push(` * @category models`)
218
+ lines.push(` */`)
219
+
220
+ const fields = generateSchemaFields(entityType.properties, entityType.navigationProperties, dataModel)
221
+
222
+ lines.push(`export class ${entityType.name} extends Schema.Class<${entityType.name}>("${entityType.name}")({`)
223
+ for (const f of fields) lines.push(` ${f}`)
224
+ lines.push(`}) {}`)
225
+
226
+ // ID type
227
+ if (entityType.keys.length > 0) {
228
+ lines.push(``)
229
+ lines.push(`/**`)
230
+ lines.push(` * ${entityType.odataName} ID type.`)
231
+ lines.push(` *`)
232
+ lines.push(` * @since 1.0.0`)
233
+ lines.push(` * @category models`)
234
+ lines.push(` */`)
235
+
236
+ const idTypeName = getIdTypeName(entityType.name)
237
+
238
+ if (entityType.keys.length === 1) {
239
+ const key = entityType.keys[0]
240
+ const keySchema = getPropertySchemaType(key, false)
241
+ lines.push(`export const ${idTypeName} = Schema.Union(`)
242
+ lines.push(` ${keySchema},`)
243
+ lines.push(` Schema.Struct({ ${key.name}: ${keySchema} })`)
244
+ lines.push(`)`)
245
+ } else {
246
+ // Composite key
247
+ const keyFields = entityType.keys.map((k) => {
248
+ const schema = getPropertySchemaType(k, false)
249
+ return `${k.name}: ${schema}`
250
+ })
251
+ lines.push(`export const ${idTypeName} = Schema.Union(`)
252
+ lines.push(` Schema.String,`)
253
+ lines.push(` Schema.Struct({ ${keyFields.join(", ")} })`)
254
+ lines.push(`)`)
255
+ }
256
+ lines.push(`export type ${idTypeName} = Schema.Schema.Type<typeof ${idTypeName}>`)
257
+ }
258
+
259
+ // Editable type
260
+ lines.push(``)
261
+ lines.push(`/**`)
262
+ lines.push(` * Editable ${entityType.odataName} for creating/updating operations.`)
263
+ lines.push(` *`)
264
+ lines.push(` * @since 1.0.0`)
265
+ lines.push(` * @category models`)
266
+ lines.push(` */`)
267
+
268
+ const editableFields = generateEditableSchemaFields(
269
+ entityType.properties.filter((p) => !p.isKey)
270
+ )
271
+ const editableName = getEditableTypeName(entityType.name)
272
+
273
+ lines.push(`export const ${editableName} = Schema.Struct({`)
274
+ for (const f of editableFields) lines.push(` ${f}`)
275
+ lines.push(`})`)
276
+ lines.push(`export type ${editableName} = Schema.Schema.Type<typeof ${editableName}>`)
277
+
278
+ return lines
279
+ }
280
+
281
+ /**
282
+ * Generate schema field definitions for a class.
283
+ * Note: Navigation properties are excluded to avoid circular reference issues.
284
+ * They can be loaded via $expand queries.
285
+ */
286
+ const generateSchemaFields = (
287
+ properties: ReadonlyArray<PropertyModel>,
288
+ _navigationProperties: ReadonlyArray<NavigationPropertyModel>,
289
+ _dataModel: DataModel
290
+ ): Array<string> => {
291
+ const fields: Array<string> = []
292
+
293
+ for (let i = 0; i < properties.length; i++) {
294
+ const prop = properties[i]
295
+ const schemaType = getPropertySchemaType(prop, prop.isNullable && !prop.isKey)
296
+ const isLast = i === properties.length - 1
297
+ fields.push(`${prop.name}: ${schemaType}${isLast ? "" : ","}`)
298
+ }
299
+
300
+ // Navigation properties are intentionally excluded from Schema.Class
301
+ // to avoid circular reference issues. They can be loaded via $expand.
302
+
303
+ return fields
304
+ }
305
+
306
+ /**
307
+ * Generate schema field definitions for an editable struct.
308
+ */
309
+ const generateEditableSchemaFields = (
310
+ properties: ReadonlyArray<PropertyModel>
311
+ ): Array<string> => {
312
+ const fields: Array<string> = []
313
+
314
+ for (let i = 0; i < properties.length; i++) {
315
+ const prop = properties[i]
316
+ const schemaType = getPropertySchemaType(prop, prop.isNullable)
317
+ const isLast = i === properties.length - 1
318
+ fields.push(`${prop.name}: ${schemaType}${isLast ? "" : ","}`)
319
+ }
320
+
321
+ return fields
322
+ }
323
+
324
+ /**
325
+ * Get the Schema type for a property.
326
+ */
327
+ const getPropertySchemaType = (
328
+ prop: PropertyModel,
329
+ makeOptional: boolean
330
+ ): string => {
331
+ let baseType = prop.typeMapping.effectSchema
332
+
333
+ // Handle collection
334
+ if (prop.isCollection) {
335
+ baseType = `Schema.Array(${baseType})`
336
+ }
337
+
338
+ // Handle nullable/optional
339
+ if (makeOptional) {
340
+ return `Schema.optionalWith(${baseType}, { nullable: true })`
341
+ }
342
+
343
+ return baseType
344
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Naming utilities for code generation.
3
+ *
4
+ * @since 1.0.0
5
+ */
6
+
7
+ /**
8
+ * Convert a string to PascalCase.
9
+ *
10
+ * @example
11
+ * toPascalCase("hello_world") // "HelloWorld"
12
+ * toPascalCase("helloWorld") // "HelloWorld"
13
+ *
14
+ * @since 1.0.0
15
+ * @category naming
16
+ */
17
+ export const toPascalCase = (str: string): string => {
18
+ return str
19
+ .replace(/[-_](.)/g, (_, char) => char.toUpperCase())
20
+ .replace(/^(.)/, (char) => char.toUpperCase())
21
+ }
22
+
23
+ /**
24
+ * Convert a string to camelCase.
25
+ *
26
+ * @example
27
+ * toCamelCase("HelloWorld") // "helloWorld"
28
+ * toCamelCase("hello_world") // "helloWorld"
29
+ *
30
+ * @since 1.0.0
31
+ * @category naming
32
+ */
33
+ export const toCamelCase = (str: string): string => {
34
+ const pascal = toPascalCase(str)
35
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1)
36
+ }
37
+
38
+ /**
39
+ * Ensure a string is a valid TypeScript identifier.
40
+ * Prefixes with underscore if it starts with a number.
41
+ *
42
+ * @since 1.0.0
43
+ * @category naming
44
+ */
45
+ export const toValidIdentifier = (str: string): string => {
46
+ // If starts with a number, prefix with underscore
47
+ if (/^[0-9]/.test(str)) {
48
+ return `_${str}`
49
+ }
50
+ // Replace invalid characters with underscores
51
+ return str.replace(/[^a-zA-Z0-9_$]/g, "_")
52
+ }
53
+
54
+ /**
55
+ * Get the TypeScript property name for an OData property.
56
+ *
57
+ * @since 1.0.0
58
+ * @category naming
59
+ */
60
+ export const getPropertyName = (odataName: string): string => {
61
+ return toCamelCase(toValidIdentifier(odataName))
62
+ }
63
+
64
+ /**
65
+ * Get the TypeScript class name for an OData entity or complex type.
66
+ *
67
+ * @since 1.0.0
68
+ * @category naming
69
+ */
70
+ export const getClassName = (odataName: string): string => {
71
+ return toPascalCase(toValidIdentifier(odataName))
72
+ }
73
+
74
+ /**
75
+ * Get the service class name for an entity set.
76
+ *
77
+ * @example
78
+ * getServiceClassName("Products") // "ProductService"
79
+ *
80
+ * @since 1.0.0
81
+ * @category naming
82
+ */
83
+ export const getServiceClassName = (entitySetName: string): string => {
84
+ // Remove trailing 's' if present to singularize
85
+ let singular = entitySetName
86
+ if (singular.endsWith("ies")) {
87
+ singular = singular.slice(0, -3) + "y"
88
+ } else if (singular.endsWith("es") && !singular.endsWith("ses")) {
89
+ singular = singular.slice(0, -2)
90
+ } else if (singular.endsWith("s") && !singular.endsWith("ss")) {
91
+ singular = singular.slice(0, -1)
92
+ }
93
+ return `${toPascalCase(singular)}Service`
94
+ }
95
+
96
+ /**
97
+ * Get the query paths interface name for a type.
98
+ *
99
+ * @example
100
+ * getQueryInterfaceName("Product") // "QProduct"
101
+ *
102
+ * @since 1.0.0
103
+ * @category naming
104
+ */
105
+ export const getQueryInterfaceName = (typeName: string): string => {
106
+ return `Q${toPascalCase(typeName)}`
107
+ }
108
+
109
+ /**
110
+ * Get the query paths instance name for a type.
111
+ *
112
+ * @example
113
+ * getQueryInstanceName("Product") // "qProduct"
114
+ *
115
+ * @since 1.0.0
116
+ * @category naming
117
+ */
118
+ export const getQueryInstanceName = (typeName: string): string => {
119
+ return `q${toPascalCase(typeName)}`
120
+ }
121
+
122
+ /**
123
+ * Get the query builder factory function name for a type.
124
+ *
125
+ * @example
126
+ * getQueryFactoryName("Product") // "productQuery"
127
+ *
128
+ * @since 1.0.0
129
+ * @category naming
130
+ */
131
+ export const getQueryFactoryName = (typeName: string): string => {
132
+ return `${toCamelCase(typeName)}Query`
133
+ }
134
+
135
+ /**
136
+ * Get the editable type name for an entity type.
137
+ *
138
+ * @example
139
+ * getEditableTypeName("Product") // "EditableProduct"
140
+ *
141
+ * @since 1.0.0
142
+ * @category naming
143
+ */
144
+ export const getEditableTypeName = (typeName: string): string => {
145
+ return `Editable${toPascalCase(typeName)}`
146
+ }
147
+
148
+ /**
149
+ * Get the ID type name for an entity type.
150
+ *
151
+ * @example
152
+ * getIdTypeName("Product") // "ProductId"
153
+ *
154
+ * @since 1.0.0
155
+ * @category naming
156
+ */
157
+ export const getIdTypeName = (typeName: string): string => {
158
+ return `${toPascalCase(typeName)}Id`
159
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Generator for package configuration files.
3
+ *
4
+ * @since 1.0.0
5
+ */
6
+ import type { DataModel } from "../model/DataModel.js"
7
+
8
+ export interface PackageConfig {
9
+ readonly packageName: string
10
+ readonly serviceName: string
11
+ }
12
+
13
+ /**
14
+ * Generate package.json content.
15
+ *
16
+ * @since 1.0.0
17
+ * @category generation
18
+ */
19
+ export const generatePackageJson = (
20
+ dataModel: DataModel,
21
+ config: PackageConfig
22
+ ): string => {
23
+ const packageJson = {
24
+ name: config.packageName,
25
+ version: "0.0.0",
26
+ type: "module",
27
+ license: "MIT",
28
+ description: `Effect-based OData client for ${dataModel.serviceName} service`,
29
+ publishConfig: {
30
+ access: "public",
31
+ directory: "dist"
32
+ },
33
+ scripts: {
34
+ codegen: "build-utils prepare-v2",
35
+ build: "pnpm build-esm && pnpm build-annotate && pnpm build-cjs && build-utils pack-v2",
36
+ "build-esm": "tsc -b tsconfig.build.json",
37
+ "build-cjs":
38
+ "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps",
39
+ "build-annotate": "babel build/esm --plugins annotate-pure-calls --out-dir build/esm --source-maps",
40
+ check: "tsc -b tsconfig.json",
41
+ test: "vitest",
42
+ coverage: "vitest --coverage"
43
+ },
44
+ dependencies: {
45
+ "@effect/platform": "^0.94.0",
46
+ "@odata-effect/odata-effect": "workspace:*",
47
+ "@odata-effect/odata-effect-promise": "workspace:*",
48
+ effect: "^3.10.7"
49
+ },
50
+ devDependencies: {
51
+ "@odata-effect/odata-effect": "workspace:*",
52
+ "@odata-effect/odata-effect-promise": "workspace:*"
53
+ },
54
+ effect: {
55
+ generateExports: {
56
+ include: ["**/*.ts"]
57
+ },
58
+ generateIndex: {
59
+ include: ["**/*.ts"]
60
+ }
61
+ }
62
+ }
63
+
64
+ return JSON.stringify(packageJson, null, 2)
65
+ }
66
+
67
+ /**
68
+ * Generate tsconfig.json content.
69
+ *
70
+ * @since 1.0.0
71
+ * @category generation
72
+ */
73
+ export const generateTsconfig = (): string => {
74
+ const tsconfig = {
75
+ extends: "../../tsconfig.base.json",
76
+ include: [],
77
+ references: [
78
+ { path: "tsconfig.src.json" },
79
+ { path: "tsconfig.test.json" }
80
+ ]
81
+ }
82
+
83
+ return JSON.stringify(tsconfig, null, 2)
84
+ }
85
+
86
+ /**
87
+ * Generate tsconfig.src.json content.
88
+ *
89
+ * @since 1.0.0
90
+ * @category generation
91
+ */
92
+ export const generateTsconfigSrc = (): string => {
93
+ const tsconfig = {
94
+ extends: "../../tsconfig.base.json",
95
+ include: ["src"],
96
+ references: [
97
+ { path: "../ODataEffect/tsconfig.src.json" },
98
+ { path: "../ODataEffectPromise/tsconfig.src.json" }
99
+ ],
100
+ compilerOptions: {
101
+ types: ["node"],
102
+ outDir: "build/src",
103
+ tsBuildInfoFile: ".tsbuildinfo/src.tsbuildinfo",
104
+ rootDir: "src"
105
+ }
106
+ }
107
+
108
+ return JSON.stringify(tsconfig, null, 2)
109
+ }
110
+
111
+ /**
112
+ * Generate tsconfig.test.json content.
113
+ *
114
+ * @since 1.0.0
115
+ * @category generation
116
+ */
117
+ export const generateTsconfigTest = (): string => {
118
+ const tsconfig = {
119
+ extends: "../../tsconfig.base.json",
120
+ include: ["test"],
121
+ references: [
122
+ { path: "tsconfig.src.json" },
123
+ { path: "../ODataEffect/tsconfig.src.json" },
124
+ { path: "../ODataEffectPromise/tsconfig.src.json" }
125
+ ],
126
+ compilerOptions: {
127
+ types: ["node"],
128
+ tsBuildInfoFile: ".tsbuildinfo/test.tsbuildinfo",
129
+ rootDir: "test",
130
+ noEmit: true
131
+ }
132
+ }
133
+
134
+ return JSON.stringify(tsconfig, null, 2)
135
+ }
136
+
137
+ /**
138
+ * Generate tsconfig.build.json content.
139
+ *
140
+ * @since 1.0.0
141
+ * @category generation
142
+ */
143
+ export const generateTsconfigBuild = (): string => {
144
+ const tsconfig = {
145
+ extends: "./tsconfig.src.json",
146
+ references: [
147
+ { path: "../ODataEffect/tsconfig.build.json" },
148
+ { path: "../ODataEffectPromise/tsconfig.build.json" }
149
+ ],
150
+ compilerOptions: {
151
+ types: ["node"],
152
+ tsBuildInfoFile: ".tsbuildinfo/build.tsbuildinfo",
153
+ outDir: "build/esm",
154
+ declarationDir: "build/dts",
155
+ stripInternal: true
156
+ }
157
+ }
158
+
159
+ return JSON.stringify(tsconfig, null, 2)
160
+ }
161
+
162
+ /**
163
+ * Generate vitest.config.ts content.
164
+ *
165
+ * @since 1.0.0
166
+ * @category generation
167
+ */
168
+ export const generateVitestConfig = (): string => {
169
+ return `import { mergeConfig, type UserConfigExport } from "vitest/config"
170
+ import shared from "../../vitest.shared.js"
171
+
172
+ const config: UserConfigExport = {}
173
+
174
+ export default mergeConfig(shared, config)
175
+ `
176
+ }