@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.
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.