@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
|
@@ -10,6 +10,8 @@ import * as Schema from "effect/Schema"
|
|
|
10
10
|
import type { DataModel } from "../model/DataModel.js"
|
|
11
11
|
import { generateIndex } from "./IndexGenerator.js"
|
|
12
12
|
import { generateModels } from "./ModelsGenerator.js"
|
|
13
|
+
import { generateNavigations } from "./NavigationGenerator.js"
|
|
14
|
+
import { generateOperations } from "./OperationsGenerator.js"
|
|
13
15
|
import {
|
|
14
16
|
generatePackageJson,
|
|
15
17
|
generateTsconfig,
|
|
@@ -21,7 +23,6 @@ import {
|
|
|
21
23
|
} from "./PackageGenerator.js"
|
|
22
24
|
import { generateQueryModels } from "./QueryModelsGenerator.js"
|
|
23
25
|
import { generateServiceFns } from "./ServiceFnGenerator.js"
|
|
24
|
-
import { generatePromiseServiceFns } from "./ServiceFnPromiseGenerator.js"
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Generator configuration.
|
|
@@ -34,6 +35,8 @@ export interface GeneratorConfig {
|
|
|
34
35
|
readonly packageName?: string
|
|
35
36
|
readonly serviceName?: string
|
|
36
37
|
readonly force?: boolean
|
|
38
|
+
/** Generate only source files directly in outputDir (no package.json, tsconfig, src/ subdirectory) */
|
|
39
|
+
readonly filesOnly?: boolean
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/**
|
|
@@ -75,6 +78,10 @@ export const generate = (
|
|
|
75
78
|
const outputDir = config.outputDir
|
|
76
79
|
const serviceName = config.serviceName ?? dataModel.serviceName
|
|
77
80
|
const packageName = config.packageName ?? `@template/${serviceName.toLowerCase()}-effect`
|
|
81
|
+
const filesOnly = config.filesOnly ?? false
|
|
82
|
+
|
|
83
|
+
// When filesOnly is true, output directly to outputDir; otherwise use outputDir/src
|
|
84
|
+
const sourceDir = filesOnly ? outputDir : path.join(outputDir, "src")
|
|
78
85
|
|
|
79
86
|
const packageConfig: PackageConfig = {
|
|
80
87
|
packageName,
|
|
@@ -84,35 +91,47 @@ export const generate = (
|
|
|
84
91
|
// Generate tree-shakable service function files
|
|
85
92
|
const serviceResult = generateServiceFns(dataModel)
|
|
86
93
|
|
|
87
|
-
// Generate
|
|
88
|
-
const
|
|
94
|
+
// Generate operations file (FunctionImports, Functions, Actions)
|
|
95
|
+
const operationsResult = generateOperations(dataModel)
|
|
96
|
+
|
|
97
|
+
// Generate navigation builders
|
|
98
|
+
const navigationResult = generateNavigations(dataModel)
|
|
89
99
|
|
|
90
|
-
// Generate
|
|
91
|
-
const
|
|
92
|
-
// Source files
|
|
100
|
+
// Generate source files
|
|
101
|
+
const sourceFiles: Array<GeneratedFile> = [
|
|
93
102
|
{
|
|
94
|
-
path: path.join(
|
|
103
|
+
path: path.join(sourceDir, "Models.ts"),
|
|
95
104
|
content: generateModels(dataModel)
|
|
96
105
|
},
|
|
97
106
|
{
|
|
98
|
-
path: path.join(
|
|
107
|
+
path: path.join(sourceDir, "QueryModels.ts"),
|
|
99
108
|
content: generateQueryModels(dataModel)
|
|
100
109
|
},
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
path: path.join(
|
|
104
|
-
content:
|
|
105
|
-
}
|
|
106
|
-
//
|
|
107
|
-
...
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
// Services file (all entity CRUD services in one file)
|
|
111
|
+
{
|
|
112
|
+
path: path.join(sourceDir, serviceResult.servicesFile.fileName),
|
|
113
|
+
content: serviceResult.servicesFile.content
|
|
114
|
+
},
|
|
115
|
+
// Operations file (only if there are unbound operations)
|
|
116
|
+
...(operationsResult.operationsFile
|
|
117
|
+
? [{
|
|
118
|
+
path: path.join(sourceDir, operationsResult.operationsFile.fileName),
|
|
119
|
+
content: operationsResult.operationsFile.content
|
|
120
|
+
}]
|
|
121
|
+
: []),
|
|
122
|
+
// Navigation builder files
|
|
123
|
+
...navigationResult.navigationFiles.map((nav) => ({
|
|
124
|
+
path: path.join(sourceDir, nav.fileName),
|
|
125
|
+
content: nav.content
|
|
110
126
|
})),
|
|
111
127
|
{
|
|
112
|
-
path: path.join(
|
|
128
|
+
path: path.join(sourceDir, "index.ts"),
|
|
113
129
|
content: generateIndex(dataModel)
|
|
114
|
-
}
|
|
115
|
-
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
// Package configuration files (only when not filesOnly)
|
|
134
|
+
const packageFiles: Array<GeneratedFile> = filesOnly ? [] : [
|
|
116
135
|
{
|
|
117
136
|
path: path.join(outputDir, "package.json"),
|
|
118
137
|
content: generatePackageJson(dataModel, packageConfig)
|
|
@@ -139,11 +158,13 @@ export const generate = (
|
|
|
139
158
|
}
|
|
140
159
|
]
|
|
141
160
|
|
|
142
|
-
|
|
143
|
-
|
|
161
|
+
const files = [...sourceFiles, ...packageFiles]
|
|
162
|
+
|
|
163
|
+
// Create output directory
|
|
164
|
+
yield* fs.makeDirectory(sourceDir, { recursive: true }).pipe(
|
|
144
165
|
Effect.mapError((error) =>
|
|
145
166
|
new GeneratorError({
|
|
146
|
-
message: `Failed to create output directory: ${
|
|
167
|
+
message: `Failed to create output directory: ${sourceDir}`,
|
|
147
168
|
cause: error
|
|
148
169
|
})
|
|
149
170
|
)
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
getQueryInterfaceName,
|
|
13
13
|
getServiceClassName
|
|
14
14
|
} from "./NamingHelper.js"
|
|
15
|
-
import {
|
|
15
|
+
import { getPathBuildersModuleName } from "./NavigationGenerator.js"
|
|
16
|
+
import { getOperationsModuleName } from "./OperationsGenerator.js"
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Generate the index.ts file content.
|
|
@@ -63,93 +64,41 @@ export const generateIndex = (dataModel: DataModel): string => {
|
|
|
63
64
|
lines.push(` ${modelExports[i]}${isLast ? "" : ","}`)
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
lines.push(`} from "./Models
|
|
67
|
+
lines.push(`} from "./Models"`)
|
|
67
68
|
lines.push(``)
|
|
68
69
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
lines.push(`//
|
|
70
|
+
// Entity Services (all in one file using crud factory)
|
|
71
|
+
lines.push(`// Entity Services`)
|
|
72
|
+
lines.push(`// Use toPromise(runtime) from PathBuilders to convert Effect to Promise`)
|
|
72
73
|
lines.push(`export {`)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
lines.push(` SapErrorResolution,`)
|
|
77
|
-
lines.push(` SapApplication,`)
|
|
78
|
-
lines.push(` SapInnerError,`)
|
|
79
|
-
lines.push(` SapErrorMessage,`)
|
|
80
|
-
lines.push(` SapErrorBody,`)
|
|
81
|
-
lines.push(` SapErrorResponse,`)
|
|
82
|
-
lines.push(` SapError,`)
|
|
74
|
+
const serviceExports: Array<string> = []
|
|
75
|
+
for (const entitySet of dataModel.entitySets.values()) {
|
|
76
|
+
serviceExports.push(getServiceClassName(entitySet.name))
|
|
83
77
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
lines.push(`
|
|
91
|
-
lines.push(` ODataV4CollectionResponse,`)
|
|
92
|
-
lines.push(` ODataV4ValueResponse,`)
|
|
93
|
-
lines.push(` ODataV4Annotations,`)
|
|
94
|
-
lines.push(` ODataV4ClientConfig,`)
|
|
95
|
-
lines.push(` buildEntityPathV4,`)
|
|
96
|
-
lines.push(` type ODataV4QueryOptions,`)
|
|
97
|
-
lines.push(` type ODataV4RequestOptions,`)
|
|
98
|
-
lines.push(` type ODataV4ClientConfigService,`)
|
|
99
|
-
lines.push(` type PagedResultV4,`)
|
|
100
|
-
} else {
|
|
101
|
-
// V2 exports
|
|
102
|
-
lines.push(` // OData V2`)
|
|
103
|
-
lines.push(` OData,`)
|
|
104
|
-
lines.push(` ODataSingleResponse,`)
|
|
105
|
-
lines.push(` ODataCollectionResponse,`)
|
|
106
|
-
lines.push(` ODataCollectionResponseWithMeta,`)
|
|
107
|
-
lines.push(` ODataValueResponse,`)
|
|
108
|
-
lines.push(` EntityMetadata,`)
|
|
109
|
-
lines.push(` MediaMetadata,`)
|
|
110
|
-
lines.push(` DeferredContent,`)
|
|
111
|
-
lines.push(` ODataClientConfig,`)
|
|
112
|
-
lines.push(` buildEntityPath,`)
|
|
113
|
-
lines.push(` DEFAULT_HEADERS,`)
|
|
114
|
-
lines.push(` MERGE_HEADERS,`)
|
|
115
|
-
lines.push(` type ODataQueryOptions,`)
|
|
116
|
-
lines.push(` type ODataRequestOptions,`)
|
|
117
|
-
lines.push(` type ODataClientConfigService,`)
|
|
118
|
-
lines.push(` type PagedResult,`)
|
|
78
|
+
// Also export types
|
|
79
|
+
serviceExports.push(`type CrudError`)
|
|
80
|
+
serviceExports.push(`type CrudContext`)
|
|
81
|
+
serviceExports.push(`type CrudService`)
|
|
82
|
+
for (let i = 0; i < serviceExports.length; i++) {
|
|
83
|
+
const isLast = i === serviceExports.length - 1
|
|
84
|
+
lines.push(` ${serviceExports[i]}${isLast ? "" : ","}`)
|
|
119
85
|
}
|
|
120
|
-
lines.push(`
|
|
121
|
-
lines.push(` FilterExpression,`)
|
|
122
|
-
lines.push(` StringPath,`)
|
|
123
|
-
lines.push(` NumberPath,`)
|
|
124
|
-
lines.push(` BooleanPath,`)
|
|
125
|
-
lines.push(` DateTimePath,`)
|
|
126
|
-
lines.push(` EntityPath,`)
|
|
127
|
-
lines.push(` CollectionPath,`)
|
|
128
|
-
lines.push(` QueryBuilder,`)
|
|
129
|
-
lines.push(` createQueryPaths,`)
|
|
130
|
-
lines.push(` createQueryBuilder,`)
|
|
131
|
-
lines.push(` type FieldToPath,`)
|
|
132
|
-
lines.push(` type QueryPaths,`)
|
|
133
|
-
lines.push(` type SelectableKeys,`)
|
|
134
|
-
lines.push(` type ExpandableKeys,`)
|
|
135
|
-
lines.push(` type BuiltQuery`)
|
|
136
|
-
lines.push(`} from "@odata-effect/odata-effect"`)
|
|
86
|
+
lines.push(`} from "./Services"`)
|
|
137
87
|
lines.push(``)
|
|
138
88
|
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
89
|
+
// Operations (FunctionImports, Functions, Actions) - only if there are unbound operations
|
|
90
|
+
const hasUnboundOperations = Array.from(dataModel.operations.values()).some((op) => !op.isBound)
|
|
91
|
+
if (hasUnboundOperations) {
|
|
92
|
+
lines.push(`// Operations (FunctionImports, Functions, Actions)`)
|
|
93
|
+
const operationsModuleName = getOperationsModuleName()
|
|
94
|
+
lines.push(`export * as ${operationsModuleName} from "./${operationsModuleName}"`)
|
|
95
|
+
lines.push(``)
|
|
144
96
|
}
|
|
145
|
-
lines.push(``)
|
|
146
97
|
|
|
147
|
-
//
|
|
148
|
-
lines.push(`//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
lines.push(`export * as ${promiseServiceName} from "./${promiseServiceName}.js"`)
|
|
152
|
-
}
|
|
98
|
+
// Path Builders (tree-shakable navigation)
|
|
99
|
+
lines.push(`// Path Builders (tree-shakable navigation)`)
|
|
100
|
+
const pathBuildersModuleName = getPathBuildersModuleName()
|
|
101
|
+
lines.push(`export * from "./${pathBuildersModuleName}"`)
|
|
153
102
|
lines.push(``)
|
|
154
103
|
|
|
155
104
|
// Query Models
|
|
@@ -183,7 +132,7 @@ export const generateIndex = (dataModel: DataModel): string => {
|
|
|
183
132
|
lines.push(` ${queryExports[i]}${isLast ? "" : ","}`)
|
|
184
133
|
}
|
|
185
134
|
|
|
186
|
-
lines.push(`} from "./QueryModels
|
|
135
|
+
lines.push(`} from "./QueryModels"`)
|
|
187
136
|
|
|
188
137
|
return lines.join("\n")
|
|
189
138
|
}
|
|
@@ -243,15 +243,12 @@ const generateEntityType = (
|
|
|
243
243
|
lines.push(` Schema.Struct({ ${key.name}: ${keySchema} })`)
|
|
244
244
|
lines.push(`)`)
|
|
245
245
|
} else {
|
|
246
|
-
// Composite key
|
|
246
|
+
// Composite key - only struct form makes sense
|
|
247
247
|
const keyFields = entityType.keys.map((k) => {
|
|
248
248
|
const schema = getPropertySchemaType(k, false)
|
|
249
249
|
return `${k.name}: ${schema}`
|
|
250
250
|
})
|
|
251
|
-
lines.push(`export const ${idTypeName} = Schema.
|
|
252
|
-
lines.push(` Schema.String,`)
|
|
253
|
-
lines.push(` Schema.Struct({ ${keyFields.join(", ")} })`)
|
|
254
|
-
lines.push(`)`)
|
|
251
|
+
lines.push(`export const ${idTypeName} = Schema.Struct({ ${keyFields.join(", ")} })`)
|
|
255
252
|
}
|
|
256
253
|
lines.push(`export type ${idTypeName} = Schema.Schema.Type<typeof ${idTypeName}>`)
|
|
257
254
|
}
|
|
@@ -84,10 +84,17 @@ export const getServiceClassName = (entitySetName: string): string => {
|
|
|
84
84
|
// Remove trailing 's' if present to singularize
|
|
85
85
|
let singular = entitySetName
|
|
86
86
|
if (singular.endsWith("ies")) {
|
|
87
|
+
// "Categories" -> "Category"
|
|
87
88
|
singular = singular.slice(0, -3) + "y"
|
|
88
|
-
} else if (
|
|
89
|
+
} else if (
|
|
90
|
+
singular.endsWith("xes") || singular.endsWith("ches") ||
|
|
91
|
+
singular.endsWith("shes") || singular.endsWith("sses") ||
|
|
92
|
+
singular.endsWith("zes")
|
|
93
|
+
) {
|
|
94
|
+
// "Boxes" -> "Box", "Beaches" -> "Beach", etc.
|
|
89
95
|
singular = singular.slice(0, -2)
|
|
90
96
|
} else if (singular.endsWith("s") && !singular.endsWith("ss")) {
|
|
97
|
+
// "Products" -> "Product", "Airlines" -> "Airline"
|
|
91
98
|
singular = singular.slice(0, -1)
|
|
92
99
|
}
|
|
93
100
|
return `${toPascalCase(singular)}Service`
|