@pands/sdk-gen 0.0.0-stage → 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.
- package/dist/index.d.ts +40 -0
- package/dist/index.js +238 -0
- package/package.json +38 -3
- package/README.md +0 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
interface GenerateSwiftSdkOptions {
|
|
2
|
+
openApiSpec: Record<string, any>;
|
|
3
|
+
outDir: string;
|
|
4
|
+
}
|
|
5
|
+
interface GeneratedSwiftSdkArtifacts {
|
|
6
|
+
openApiPath: string;
|
|
7
|
+
configYamlPath: string;
|
|
8
|
+
clientSwiftPath: string;
|
|
9
|
+
}
|
|
10
|
+
declare function generateSwiftSdk(options: GenerateSwiftSdkOptions): GeneratedSwiftSdkArtifacts;
|
|
11
|
+
|
|
12
|
+
interface GenerateKotlinSdkOptions {
|
|
13
|
+
openApiSpec: Record<string, any>;
|
|
14
|
+
outDir: string;
|
|
15
|
+
projectName: string;
|
|
16
|
+
}
|
|
17
|
+
interface GeneratedKotlinSdkArtifacts {
|
|
18
|
+
modelsKtPath: string;
|
|
19
|
+
apiKtPath: string;
|
|
20
|
+
}
|
|
21
|
+
declare function generateKotlinSdk(options: GenerateKotlinSdkOptions): GeneratedKotlinSdkArtifacts;
|
|
22
|
+
|
|
23
|
+
interface GenerateTypeScriptSdkOptions {
|
|
24
|
+
openApiSpec: Record<string, any>;
|
|
25
|
+
outDir: string;
|
|
26
|
+
checkOnly?: boolean;
|
|
27
|
+
}
|
|
28
|
+
interface GeneratedTypeScriptSdkArtifacts {
|
|
29
|
+
contractsPath: string;
|
|
30
|
+
schemaPath: string;
|
|
31
|
+
clientPath: string;
|
|
32
|
+
openApiJson: string;
|
|
33
|
+
schemaDts: string;
|
|
34
|
+
clientTs: string;
|
|
35
|
+
drifted: boolean;
|
|
36
|
+
}
|
|
37
|
+
declare const CLIENT_TS_TEMPLATE = "// Generated by @pands/sdk-gen \u2014 do not edit by hand\nimport createClient, { type Client } from \"openapi-fetch\";\nimport type { paths } from \"./schema.js\";\n\nexport type { paths };\nexport type PandApiClient = Client<paths>;\n\nexport interface CreatePandClientOptions {\n baseUrl?: string;\n getToken?: () => string | null | undefined | Promise<string | null | undefined>;\n fetch?: typeof globalThis.fetch;\n headers?: Record<string, string>;\n}\n\nexport function createPandClient(options: CreatePandClientOptions = {}): PandApiClient {\n const client = createClient<paths>({\n baseUrl: options.baseUrl ?? \"\",\n fetch: options.fetch,\n headers: options.headers,\n });\n\n if (options.getToken) {\n client.use({\n async onRequest({ request }) {\n const token = await options.getToken?.();\n if (token) {\n request.headers.set(\"authorization\", `Bearer ${token}`);\n }\n return request;\n },\n });\n }\n\n return client;\n}\n\nexport const createPandSdk = createPandClient;\n";
|
|
38
|
+
declare function generateTypeScriptSdk(options: GenerateTypeScriptSdkOptions): Promise<GeneratedTypeScriptSdkArtifacts>;
|
|
39
|
+
|
|
40
|
+
export { CLIENT_TS_TEMPLATE, type GenerateKotlinSdkOptions, type GenerateSwiftSdkOptions, type GenerateTypeScriptSdkOptions, type GeneratedKotlinSdkArtifacts, type GeneratedSwiftSdkArtifacts, type GeneratedTypeScriptSdkArtifacts, generateKotlinSdk, generateSwiftSdk, generateTypeScriptSdk };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import fs3 from "fs";
|
|
3
|
+
import path3 from "path";
|
|
4
|
+
import openapiTS, { astToString } from "openapi-typescript";
|
|
5
|
+
|
|
6
|
+
// src/swift.ts
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
function generateSwiftSdk(options) {
|
|
10
|
+
const sdkDir = path.join(
|
|
11
|
+
options.outDir,
|
|
12
|
+
"apps",
|
|
13
|
+
"ios",
|
|
14
|
+
"Packages",
|
|
15
|
+
"PandSDK",
|
|
16
|
+
"Sources",
|
|
17
|
+
"PandSDK"
|
|
18
|
+
);
|
|
19
|
+
fs.mkdirSync(sdkDir, { recursive: true });
|
|
20
|
+
const openApiPath = path.join(sdkDir, "openapi.json");
|
|
21
|
+
const configYamlPath = path.join(sdkDir, "openapi-generator-config.yaml");
|
|
22
|
+
const clientSwiftPath = path.join(sdkDir, "PandAPIClient.swift");
|
|
23
|
+
fs.writeFileSync(openApiPath, `${JSON.stringify(options.openApiSpec, null, 2)}
|
|
24
|
+
`, "utf8");
|
|
25
|
+
const configYaml = ["generate:", " - types", " - client", "accessModifier: public", ""].join(
|
|
26
|
+
"\n"
|
|
27
|
+
);
|
|
28
|
+
fs.writeFileSync(configYamlPath, configYaml, "utf8");
|
|
29
|
+
const schemas = options.openApiSpec.components?.schemas ?? {};
|
|
30
|
+
const modelStructs = [];
|
|
31
|
+
for (const [rawSchemaName, schemaObj] of Object.entries(schemas)) {
|
|
32
|
+
const schemaName = rawSchemaName.includes("_") ? rawSchemaName.slice(rawSchemaName.indexOf("_") + 1) : rawSchemaName;
|
|
33
|
+
const props = schemaObj?.properties ?? {};
|
|
34
|
+
const requiredSet = new Set(schemaObj?.required ?? []);
|
|
35
|
+
const fields = [];
|
|
36
|
+
for (const [propName, propDef] of Object.entries(props)) {
|
|
37
|
+
let swiftType = "String";
|
|
38
|
+
if (propDef.type === "boolean") swiftType = "Bool";
|
|
39
|
+
else if (propDef.type === "integer") swiftType = "Int";
|
|
40
|
+
else if (propDef.type === "number") swiftType = "Double";
|
|
41
|
+
const optionalSuffix = requiredSet.has(propName) ? "" : "?";
|
|
42
|
+
fields.push(` public var ${propName}: ${swiftType}${optionalSuffix}`);
|
|
43
|
+
}
|
|
44
|
+
modelStructs.push(
|
|
45
|
+
`public struct ${schemaName}: Codable, Identifiable, Sendable {
|
|
46
|
+
${fields.join("\n")}
|
|
47
|
+
}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const clientSwift = `// Generated by @pands/sdk-gen \u2014 do not edit by hand
|
|
51
|
+
import Foundation
|
|
52
|
+
|
|
53
|
+
${modelStructs.join("\n\n")}
|
|
54
|
+
|
|
55
|
+
public actor PandAPIClient {
|
|
56
|
+
public let baseURL: URL
|
|
57
|
+
private let tokenProvider: @Sendable () -> String?
|
|
58
|
+
|
|
59
|
+
public init(baseURL: URL, tokenProvider: @escaping @Sendable () -> String? = { nil }) {
|
|
60
|
+
self.baseURL = baseURL
|
|
61
|
+
self.tokenProvider = tokenProvider
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public func request<T: Decodable>(
|
|
65
|
+
path: String,
|
|
66
|
+
method: String = "GET",
|
|
67
|
+
body: Data? = nil
|
|
68
|
+
) async throws -> T {
|
|
69
|
+
let url = baseURL.appendingPathComponent(path)
|
|
70
|
+
var req = URLRequest(url: url)
|
|
71
|
+
req.httpMethod = method
|
|
72
|
+
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
73
|
+
if let token = tokenProvider() {
|
|
74
|
+
req.setValue("Bearer \\(token)", forHTTPHeaderField: "Authorization")
|
|
75
|
+
}
|
|
76
|
+
req.httpBody = body
|
|
77
|
+
let (data, _) = try await URLSession.shared.data(for: req)
|
|
78
|
+
return try JSONDecoder().decode(T.self, from: data)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
`;
|
|
82
|
+
fs.writeFileSync(clientSwiftPath, clientSwift, "utf8");
|
|
83
|
+
return {
|
|
84
|
+
openApiPath,
|
|
85
|
+
configYamlPath,
|
|
86
|
+
clientSwiftPath
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/kotlin.ts
|
|
91
|
+
import fs2 from "fs";
|
|
92
|
+
import path2 from "path";
|
|
93
|
+
function generateKotlinSdk(options) {
|
|
94
|
+
const snakeProject = options.projectName.replace(/-/g, "_");
|
|
95
|
+
const sdkDir = path2.join(
|
|
96
|
+
options.outDir,
|
|
97
|
+
"apps",
|
|
98
|
+
"android",
|
|
99
|
+
"app",
|
|
100
|
+
"src",
|
|
101
|
+
"main",
|
|
102
|
+
"java",
|
|
103
|
+
"dev",
|
|
104
|
+
"mahdins",
|
|
105
|
+
snakeProject,
|
|
106
|
+
"sdk"
|
|
107
|
+
);
|
|
108
|
+
fs2.mkdirSync(sdkDir, { recursive: true });
|
|
109
|
+
const modelsKtPath = path2.join(sdkDir, "Models.kt");
|
|
110
|
+
const apiKtPath = path2.join(sdkDir, "PandApi.kt");
|
|
111
|
+
const schemas = options.openApiSpec.components?.schemas ?? {};
|
|
112
|
+
const dataClasses = [];
|
|
113
|
+
for (const [rawSchemaName, schemaObj] of Object.entries(schemas)) {
|
|
114
|
+
const schemaName = rawSchemaName.includes("_") ? rawSchemaName.slice(rawSchemaName.indexOf("_") + 1) : rawSchemaName;
|
|
115
|
+
const props = schemaObj?.properties ?? {};
|
|
116
|
+
const requiredSet = new Set(schemaObj?.required ?? []);
|
|
117
|
+
const fields = [];
|
|
118
|
+
for (const [propName, propDef] of Object.entries(props)) {
|
|
119
|
+
let ktType = "String";
|
|
120
|
+
if (propDef.type === "boolean") ktType = "Boolean";
|
|
121
|
+
else if (propDef.type === "integer") ktType = "Int";
|
|
122
|
+
else if (propDef.type === "number") ktType = "Double";
|
|
123
|
+
const nullable = requiredSet.has(propName) ? "" : "? = null";
|
|
124
|
+
fields.push(` val ${propName}: ${ktType}${nullable}`);
|
|
125
|
+
}
|
|
126
|
+
dataClasses.push(`@Serializable
|
|
127
|
+
data class ${schemaName}(
|
|
128
|
+
${fields.join(",\n")}
|
|
129
|
+
)`);
|
|
130
|
+
}
|
|
131
|
+
const modelsKt = `// Generated by @pands/sdk-gen \u2014 do not edit by hand
|
|
132
|
+
package dev.mahdins.${snakeProject}.sdk
|
|
133
|
+
|
|
134
|
+
import kotlinx.serialization.Serializable
|
|
135
|
+
|
|
136
|
+
${dataClasses.join("\n\n")}
|
|
137
|
+
`;
|
|
138
|
+
const apiKt = `// Generated by @pands/sdk-gen \u2014 do not edit by hand
|
|
139
|
+
package dev.mahdins.${snakeProject}.sdk
|
|
140
|
+
|
|
141
|
+
import retrofit2.http.Body
|
|
142
|
+
import retrofit2.http.DELETE
|
|
143
|
+
import retrofit2.http.GET
|
|
144
|
+
import retrofit2.http.POST
|
|
145
|
+
import retrofit2.http.Path
|
|
146
|
+
|
|
147
|
+
interface PandApi {
|
|
148
|
+
@GET("/healthz")
|
|
149
|
+
suspend fun healthz(): Map<String, String>
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
fs2.writeFileSync(modelsKtPath, modelsKt, "utf8");
|
|
153
|
+
fs2.writeFileSync(apiKtPath, apiKt, "utf8");
|
|
154
|
+
return {
|
|
155
|
+
modelsKtPath,
|
|
156
|
+
apiKtPath
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/index.ts
|
|
161
|
+
var CLIENT_TS_TEMPLATE = `// Generated by @pands/sdk-gen \u2014 do not edit by hand
|
|
162
|
+
import createClient, { type Client } from "openapi-fetch";
|
|
163
|
+
import type { paths } from "./schema.js";
|
|
164
|
+
|
|
165
|
+
export type { paths };
|
|
166
|
+
export type PandApiClient = Client<paths>;
|
|
167
|
+
|
|
168
|
+
export interface CreatePandClientOptions {
|
|
169
|
+
baseUrl?: string;
|
|
170
|
+
getToken?: () => string | null | undefined | Promise<string | null | undefined>;
|
|
171
|
+
fetch?: typeof globalThis.fetch;
|
|
172
|
+
headers?: Record<string, string>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function createPandClient(options: CreatePandClientOptions = {}): PandApiClient {
|
|
176
|
+
const client = createClient<paths>({
|
|
177
|
+
baseUrl: options.baseUrl ?? "",
|
|
178
|
+
fetch: options.fetch,
|
|
179
|
+
headers: options.headers,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (options.getToken) {
|
|
183
|
+
client.use({
|
|
184
|
+
async onRequest({ request }) {
|
|
185
|
+
const token = await options.getToken?.();
|
|
186
|
+
if (token) {
|
|
187
|
+
request.headers.set("authorization", \`Bearer \${token}\`);
|
|
188
|
+
}
|
|
189
|
+
return request;
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return client;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export const createPandSdk = createPandClient;
|
|
198
|
+
`;
|
|
199
|
+
async function generateTypeScriptSdk(options) {
|
|
200
|
+
const openApiJson = `${JSON.stringify(options.openApiSpec, null, 2)}
|
|
201
|
+
`;
|
|
202
|
+
const ast = await openapiTS(options.openApiSpec);
|
|
203
|
+
const schemaDts = `// Generated by @pands/sdk-gen \u2014 do not edit by hand
|
|
204
|
+
${astToString(ast)}`;
|
|
205
|
+
const clientTs = CLIENT_TS_TEMPLATE;
|
|
206
|
+
const contractsDir = path3.join(options.outDir, "packages", "contracts");
|
|
207
|
+
const sdkSrcDir = path3.join(options.outDir, "packages", "sdk", "src");
|
|
208
|
+
const contractsPath = path3.join(contractsDir, "openapi.json");
|
|
209
|
+
const schemaPath = path3.join(sdkSrcDir, "schema.d.ts");
|
|
210
|
+
const clientPath = path3.join(sdkSrcDir, "index.ts");
|
|
211
|
+
const existingOpenApi = fs3.existsSync(contractsPath) ? fs3.readFileSync(contractsPath, "utf8") : null;
|
|
212
|
+
const existingSchema = fs3.existsSync(schemaPath) ? fs3.readFileSync(schemaPath, "utf8") : null;
|
|
213
|
+
const drifted = existingOpenApi !== openApiJson || existingSchema !== schemaDts;
|
|
214
|
+
if (!options.checkOnly) {
|
|
215
|
+
fs3.mkdirSync(contractsDir, { recursive: true });
|
|
216
|
+
fs3.mkdirSync(sdkSrcDir, { recursive: true });
|
|
217
|
+
fs3.writeFileSync(contractsPath, openApiJson, "utf8");
|
|
218
|
+
fs3.writeFileSync(schemaPath, schemaDts, "utf8");
|
|
219
|
+
if (!fs3.existsSync(clientPath)) {
|
|
220
|
+
fs3.writeFileSync(clientPath, clientTs, "utf8");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
contractsPath,
|
|
225
|
+
schemaPath,
|
|
226
|
+
clientPath,
|
|
227
|
+
openApiJson,
|
|
228
|
+
schemaDts,
|
|
229
|
+
clientTs,
|
|
230
|
+
drifted
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
export {
|
|
234
|
+
CLIENT_TS_TEMPLATE,
|
|
235
|
+
generateKotlinSdk,
|
|
236
|
+
generateSwiftSdk,
|
|
237
|
+
generateTypeScriptSdk
|
|
238
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pands/sdk-gen",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"openapi-fetch": "^0.13.4",
|
|
16
|
+
"openapi-typescript": "^7.6.1",
|
|
17
|
+
"typescript": "^5.7.3"
|
|
18
|
+
},
|
|
19
|
+
"description": "Multi-language SDK and OpenAPI specification generator for Pand",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": "Mehdi Nosrati",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/MehdiNosrati/pand.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/MehdiNosrati/pand#readme",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/MehdiNosrati/pand/issues"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "vitest run"
|
|
40
|
+
}
|
|
6
41
|
}
|
package/README.md
DELETED