postgresdk 0.1.2-alpha.3 → 0.2.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 +143 -42
- package/dist/cli-init.d.ts +2 -0
- package/dist/cli-pull.d.ts +1 -0
- package/dist/cli.js +525 -119
- package/dist/emit-base-client.d.ts +5 -0
- package/dist/emit-sdk-bundle.d.ts +8 -0
- package/dist/index.js +236 -100
- package/dist/types.d.ts +6 -0
- package/package.json +5 -2
package/dist/cli.js
CHANGED
@@ -17,6 +17,16 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
17
17
|
return to;
|
18
18
|
};
|
19
19
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
20
|
+
var __export = (target, all) => {
|
21
|
+
for (var name in all)
|
22
|
+
__defProp(target, name, {
|
23
|
+
get: all[name],
|
24
|
+
enumerable: true,
|
25
|
+
configurable: true,
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
27
|
+
});
|
28
|
+
};
|
29
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
20
30
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
21
31
|
|
22
32
|
// node_modules/dotenv/package.json
|
@@ -460,6 +470,242 @@ var require_config = __commonJS(() => {
|
|
460
470
|
})();
|
461
471
|
});
|
462
472
|
|
473
|
+
// src/cli-init.ts
|
474
|
+
var exports_cli_init = {};
|
475
|
+
__export(exports_cli_init, {
|
476
|
+
initCommand: () => initCommand
|
477
|
+
});
|
478
|
+
import { existsSync, writeFileSync } from "fs";
|
479
|
+
import { resolve } from "path";
|
480
|
+
async function initCommand(args) {
|
481
|
+
console.log(`\uD83D\uDE80 Initializing postgresdk configuration...
|
482
|
+
`);
|
483
|
+
const configPath = resolve(process.cwd(), "postgresdk.config.ts");
|
484
|
+
if (existsSync(configPath)) {
|
485
|
+
console.error("❌ Error: postgresdk.config.ts already exists");
|
486
|
+
console.log(" To reinitialize, please remove or rename the existing file first.");
|
487
|
+
process.exit(1);
|
488
|
+
}
|
489
|
+
const envPath = resolve(process.cwd(), ".env");
|
490
|
+
const hasEnv = existsSync(envPath);
|
491
|
+
try {
|
492
|
+
writeFileSync(configPath, CONFIG_TEMPLATE, "utf-8");
|
493
|
+
console.log("✅ Created postgresdk.config.ts");
|
494
|
+
console.log(`
|
495
|
+
\uD83D\uDCDD Next steps:`);
|
496
|
+
console.log(" 1. Edit postgresdk.config.ts with your database connection");
|
497
|
+
if (!hasEnv) {
|
498
|
+
console.log(" 2. Consider creating a .env file for sensitive values:");
|
499
|
+
console.log(" DATABASE_URL=postgres://user:pass@localhost:5432/mydb");
|
500
|
+
console.log(" API_KEY=your-secret-key");
|
501
|
+
console.log(" JWT_SECRET=your-jwt-secret");
|
502
|
+
}
|
503
|
+
console.log(" 3. Run 'postgresdk generate' to create your SDK");
|
504
|
+
console.log(`
|
505
|
+
\uD83D\uDCA1 Tip: The config file has detailed comments for all options.`);
|
506
|
+
console.log(" Uncomment the options you want to customize.");
|
507
|
+
} catch (error) {
|
508
|
+
console.error("❌ Error creating config file:", error);
|
509
|
+
process.exit(1);
|
510
|
+
}
|
511
|
+
}
|
512
|
+
var CONFIG_TEMPLATE = `/**
|
513
|
+
* PostgreSDK Configuration
|
514
|
+
*
|
515
|
+
* This file configures how postgresdk generates your SDK.
|
516
|
+
* Environment variables are automatically loaded from .env files.
|
517
|
+
*/
|
518
|
+
|
519
|
+
export default {
|
520
|
+
// ========== DATABASE CONNECTION (Required) ==========
|
521
|
+
|
522
|
+
/**
|
523
|
+
* PostgreSQL connection string
|
524
|
+
* Format: postgres://user:password@host:port/database
|
525
|
+
*/
|
526
|
+
connectionString: process.env.DATABASE_URL || "postgres://user:password@localhost:5432/mydb",
|
527
|
+
|
528
|
+
// ========== BASIC OPTIONS ==========
|
529
|
+
|
530
|
+
/**
|
531
|
+
* Database schema to introspect
|
532
|
+
* @default "public"
|
533
|
+
*/
|
534
|
+
// schema: "public",
|
535
|
+
|
536
|
+
/**
|
537
|
+
* Output directory for server-side code (routes, validators, etc.)
|
538
|
+
* @default "./generated/server"
|
539
|
+
*/
|
540
|
+
// outServer: "./generated/server",
|
541
|
+
|
542
|
+
/**
|
543
|
+
* Output directory for client SDK
|
544
|
+
* @default "./generated/client"
|
545
|
+
*/
|
546
|
+
// outClient: "./generated/client",
|
547
|
+
|
548
|
+
// ========== ADVANCED OPTIONS ==========
|
549
|
+
|
550
|
+
/**
|
551
|
+
* Column name for soft deletes. When set, DELETE operations will update
|
552
|
+
* this column instead of removing rows.
|
553
|
+
* @default null (hard deletes)
|
554
|
+
* @example "deleted_at"
|
555
|
+
*/
|
556
|
+
// softDeleteColumn: null,
|
557
|
+
|
558
|
+
/**
|
559
|
+
* Maximum depth for nested relationship includes to prevent infinite loops
|
560
|
+
* @default 3
|
561
|
+
*/
|
562
|
+
// includeDepthLimit: 3,
|
563
|
+
|
564
|
+
/**
|
565
|
+
* How to handle date/timestamp columns in TypeScript
|
566
|
+
* - "date": Use JavaScript Date objects
|
567
|
+
* - "string": Use ISO 8601 strings
|
568
|
+
* @default "date"
|
569
|
+
*/
|
570
|
+
// dateType: "date",
|
571
|
+
|
572
|
+
// ========== AUTHENTICATION ==========
|
573
|
+
|
574
|
+
/**
|
575
|
+
* Authentication configuration for your API
|
576
|
+
*
|
577
|
+
* Simple syntax examples:
|
578
|
+
* auth: { apiKey: process.env.API_KEY }
|
579
|
+
* auth: { jwt: process.env.JWT_SECRET }
|
580
|
+
*
|
581
|
+
* Multiple API keys:
|
582
|
+
* auth: { apiKeys: [process.env.KEY1, process.env.KEY2] }
|
583
|
+
*
|
584
|
+
* Full syntax for advanced options:
|
585
|
+
*/
|
586
|
+
// auth: {
|
587
|
+
// // Strategy: "none" | "api-key" | "jwt-hs256"
|
588
|
+
// strategy: "none",
|
589
|
+
//
|
590
|
+
// // For API Key authentication
|
591
|
+
// apiKeyHeader: "x-api-key", // Header name for API key
|
592
|
+
// apiKeys: [ // List of valid API keys
|
593
|
+
// process.env.API_KEY_1,
|
594
|
+
// process.env.API_KEY_2,
|
595
|
+
// ],
|
596
|
+
//
|
597
|
+
// // For JWT (HS256) authentication
|
598
|
+
// jwt: {
|
599
|
+
// sharedSecret: process.env.JWT_SECRET, // Secret for signing/verifying
|
600
|
+
// issuer: "my-app", // Optional: validate 'iss' claim
|
601
|
+
// audience: "my-users", // Optional: validate 'aud' claim
|
602
|
+
// }
|
603
|
+
// },
|
604
|
+
|
605
|
+
// ========== SDK DISTRIBUTION (Pull Configuration) ==========
|
606
|
+
|
607
|
+
/**
|
608
|
+
* Configuration for pulling SDK from a remote API
|
609
|
+
* Used when running 'postgresdk pull' command
|
610
|
+
*/
|
611
|
+
// pull: {
|
612
|
+
// from: "https://api.myapp.com", // API URL to pull SDK from
|
613
|
+
// output: "./src/sdk", // Local directory for pulled SDK
|
614
|
+
// token: process.env.API_TOKEN, // Optional authentication token
|
615
|
+
// },
|
616
|
+
};
|
617
|
+
`;
|
618
|
+
var init_cli_init = () => {};
|
619
|
+
|
620
|
+
// src/cli-pull.ts
|
621
|
+
var exports_cli_pull = {};
|
622
|
+
__export(exports_cli_pull, {
|
623
|
+
pullCommand: () => pullCommand
|
624
|
+
});
|
625
|
+
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
626
|
+
import { join as join2, dirname as dirname2, resolve as resolve2 } from "path";
|
627
|
+
import { existsSync as existsSync2 } from "fs";
|
628
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
629
|
+
async function pullCommand(args) {
|
630
|
+
let configPath = "postgresdk.config.ts";
|
631
|
+
const configIndex = args.findIndex((a) => a === "-c" || a === "--config");
|
632
|
+
if (configIndex !== -1 && args[configIndex + 1]) {
|
633
|
+
configPath = args[configIndex + 1];
|
634
|
+
}
|
635
|
+
let fileConfig = {};
|
636
|
+
const fullConfigPath = resolve2(process.cwd(), configPath);
|
637
|
+
if (existsSync2(fullConfigPath)) {
|
638
|
+
console.log(`\uD83D\uDCCB Loading ${configPath}`);
|
639
|
+
try {
|
640
|
+
const configUrl = pathToFileURL2(fullConfigPath).href;
|
641
|
+
const module = await import(configUrl);
|
642
|
+
const config2 = module.default || module;
|
643
|
+
if (config2.pull) {
|
644
|
+
fileConfig = config2.pull;
|
645
|
+
}
|
646
|
+
} catch (err) {
|
647
|
+
console.error("⚠️ Failed to load config file:", err);
|
648
|
+
}
|
649
|
+
}
|
650
|
+
const cliConfig = {
|
651
|
+
from: args.find((a) => a.startsWith("--from="))?.split("=")[1],
|
652
|
+
output: args.find((a) => a.startsWith("--output="))?.split("=")[1],
|
653
|
+
token: args.find((a) => a.startsWith("--token="))?.split("=")[1]
|
654
|
+
};
|
655
|
+
const config = {
|
656
|
+
output: "./src/sdk",
|
657
|
+
...fileConfig,
|
658
|
+
...Object.fromEntries(Object.entries(cliConfig).filter(([_, v]) => v !== undefined))
|
659
|
+
};
|
660
|
+
if (!config.from) {
|
661
|
+
console.error("❌ Missing API URL. Specify via --from or in postgresdk.config.ts");
|
662
|
+
console.error(`
|
663
|
+
Example config file:`);
|
664
|
+
console.error(`export default {
|
665
|
+
pull: {
|
666
|
+
from: "https://api.company.com",
|
667
|
+
output: "./src/sdk"
|
668
|
+
}
|
669
|
+
}`);
|
670
|
+
process.exit(1);
|
671
|
+
}
|
672
|
+
console.log(`\uD83D\uDD04 Pulling SDK from ${config.from}`);
|
673
|
+
console.log(`\uD83D\uDCC1 Output directory: ${config.output}`);
|
674
|
+
try {
|
675
|
+
const headers = config.token ? { Authorization: `Bearer ${config.token}` } : {};
|
676
|
+
const manifestRes = await fetch(`${config.from}/sdk/manifest`, { headers });
|
677
|
+
if (!manifestRes.ok) {
|
678
|
+
throw new Error(`Failed to fetch SDK manifest: ${manifestRes.status} ${manifestRes.statusText}`);
|
679
|
+
}
|
680
|
+
const manifest = await manifestRes.json();
|
681
|
+
console.log(`\uD83D\uDCE6 SDK version: ${manifest.version}`);
|
682
|
+
console.log(`\uD83D\uDCC5 Generated: ${manifest.generated}`);
|
683
|
+
console.log(`\uD83D\uDCC4 Files: ${manifest.files.length}`);
|
684
|
+
const sdkRes = await fetch(`${config.from}/sdk/download`, { headers });
|
685
|
+
if (!sdkRes.ok) {
|
686
|
+
throw new Error(`Failed to download SDK: ${sdkRes.status} ${sdkRes.statusText}`);
|
687
|
+
}
|
688
|
+
const sdk = await sdkRes.json();
|
689
|
+
for (const [path, content] of Object.entries(sdk.files)) {
|
690
|
+
const fullPath = join2(config.output, path);
|
691
|
+
await mkdir2(dirname2(fullPath), { recursive: true });
|
692
|
+
await writeFile2(fullPath, content, "utf-8");
|
693
|
+
console.log(` ✓ ${path}`);
|
694
|
+
}
|
695
|
+
await writeFile2(join2(config.output, ".postgresdk.json"), JSON.stringify({
|
696
|
+
version: sdk.version,
|
697
|
+
generated: sdk.generated,
|
698
|
+
pulledFrom: config.from,
|
699
|
+
pulledAt: new Date().toISOString()
|
700
|
+
}, null, 2));
|
701
|
+
console.log(`✅ SDK pulled successfully to ${config.output}`);
|
702
|
+
} catch (err) {
|
703
|
+
console.error(`❌ Pull failed:`, err);
|
704
|
+
process.exit(1);
|
705
|
+
}
|
706
|
+
}
|
707
|
+
var init_cli_pull = () => {};
|
708
|
+
|
463
709
|
// src/index.ts
|
464
710
|
var import_config = __toESM(require_config(), 1);
|
465
711
|
import { join } from "node:path";
|
@@ -958,13 +1204,116 @@ function emitClient(table) {
|
|
958
1204
|
const pkType = hasCompositePk ? `{ ${safePk.map((c) => `${c}: string`).join("; ")} }` : `string`;
|
959
1205
|
const pkPathExpr = hasCompositePk ? safePk.map((c) => `pk.${c}`).join(` + "/" + `) : `pk`;
|
960
1206
|
return `/* Generated. Do not edit. */
|
1207
|
+
import { BaseClient } from "./base-client";
|
961
1208
|
import type { ${Type}IncludeSpec } from "./include-spec";
|
962
1209
|
import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}";
|
963
1210
|
|
964
|
-
|
965
|
-
|
1211
|
+
/**
|
1212
|
+
* Client for ${table.name} table operations
|
1213
|
+
*/
|
1214
|
+
export class ${Type}Client extends BaseClient {
|
1215
|
+
private readonly resource = "/v1/${table.name}";
|
1216
|
+
|
1217
|
+
async create(data: Insert${Type}): Promise<Select${Type}> {
|
1218
|
+
return this.post<Select${Type}>(this.resource, data);
|
1219
|
+
}
|
966
1220
|
|
967
|
-
|
1221
|
+
async getByPk(pk: ${pkType}): Promise<Select${Type} | null> {
|
1222
|
+
const path = ${pkPathExpr};
|
1223
|
+
return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
1224
|
+
}
|
1225
|
+
|
1226
|
+
async list(params?: {
|
1227
|
+
include?: ${Type}IncludeSpec;
|
1228
|
+
limit?: number;
|
1229
|
+
offset?: number;
|
1230
|
+
where?: any;
|
1231
|
+
orderBy?: string;
|
1232
|
+
order?: "asc" | "desc";
|
1233
|
+
}): Promise<Select${Type}[]> {
|
1234
|
+
return this.post<Select${Type}[]>(\`\${this.resource}/list\`, params ?? {});
|
1235
|
+
}
|
1236
|
+
|
1237
|
+
async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null> {
|
1238
|
+
const path = ${pkPathExpr};
|
1239
|
+
return this.patch<Select${Type} | null>(\`\${this.resource}/\${path}\`, patch);
|
1240
|
+
}
|
1241
|
+
|
1242
|
+
async delete(pk: ${pkType}): Promise<Select${Type} | null> {
|
1243
|
+
const path = ${pkPathExpr};
|
1244
|
+
return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
1245
|
+
}
|
1246
|
+
}
|
1247
|
+
`;
|
1248
|
+
}
|
1249
|
+
function emitClientIndex(tables) {
|
1250
|
+
let out = `/* Generated. Do not edit. */
|
1251
|
+
`;
|
1252
|
+
out += `import { BaseClient, AuthConfig } from "./base-client";
|
1253
|
+
`;
|
1254
|
+
for (const t of tables) {
|
1255
|
+
out += `import { ${pascal(t.name)}Client } from "./${t.name}";
|
1256
|
+
`;
|
1257
|
+
}
|
1258
|
+
out += `
|
1259
|
+
// Re-export auth types for convenience
|
1260
|
+
`;
|
1261
|
+
out += `export type { AuthConfig as SDKAuth, AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client";
|
1262
|
+
|
1263
|
+
`;
|
1264
|
+
out += `/**
|
1265
|
+
`;
|
1266
|
+
out += ` * Main SDK class that provides access to all table clients
|
1267
|
+
`;
|
1268
|
+
out += ` */
|
1269
|
+
`;
|
1270
|
+
out += `export class SDK {
|
1271
|
+
`;
|
1272
|
+
for (const t of tables) {
|
1273
|
+
out += ` public ${t.name}: ${pascal(t.name)}Client;
|
1274
|
+
`;
|
1275
|
+
}
|
1276
|
+
out += `
|
1277
|
+
constructor(cfg: { baseUrl: string; fetch?: typeof fetch; auth?: AuthConfig }) {
|
1278
|
+
`;
|
1279
|
+
out += ` const f = cfg.fetch ?? fetch;
|
1280
|
+
`;
|
1281
|
+
for (const t of tables) {
|
1282
|
+
out += ` this.${t.name} = new ${pascal(t.name)}Client(cfg.baseUrl, f, cfg.auth);
|
1283
|
+
`;
|
1284
|
+
}
|
1285
|
+
out += ` }
|
1286
|
+
`;
|
1287
|
+
out += `}
|
1288
|
+
|
1289
|
+
`;
|
1290
|
+
out += `// Export individual table clients
|
1291
|
+
`;
|
1292
|
+
for (const t of tables) {
|
1293
|
+
out += `export { ${pascal(t.name)}Client } from "./${t.name}";
|
1294
|
+
`;
|
1295
|
+
}
|
1296
|
+
out += `
|
1297
|
+
// Export base client for custom extensions
|
1298
|
+
`;
|
1299
|
+
out += `export { BaseClient } from "./base-client";
|
1300
|
+
`;
|
1301
|
+
out += `
|
1302
|
+
// Export include specifications
|
1303
|
+
`;
|
1304
|
+
out += `export * from "./include-spec";
|
1305
|
+
`;
|
1306
|
+
return out;
|
1307
|
+
}
|
1308
|
+
|
1309
|
+
// src/emit-base-client.ts
|
1310
|
+
function emitBaseClient() {
|
1311
|
+
return `/* Generated. Do not edit. */
|
1312
|
+
|
1313
|
+
export type HeaderMap = Record<string, string>;
|
1314
|
+
export type AuthHeadersProvider = () => Promise<HeaderMap> | HeaderMap;
|
1315
|
+
|
1316
|
+
export type AuthConfig =
|
968
1317
|
| AuthHeadersProvider
|
969
1318
|
| {
|
970
1319
|
apiKey?: string;
|
@@ -976,14 +1325,18 @@ type AuthConfig =
|
|
976
1325
|
headers?: AuthHeadersProvider;
|
977
1326
|
};
|
978
1327
|
|
979
|
-
|
1328
|
+
/**
|
1329
|
+
* Base client class with shared authentication and request handling logic.
|
1330
|
+
* All table-specific clients extend this class.
|
1331
|
+
*/
|
1332
|
+
export abstract class BaseClient {
|
980
1333
|
constructor(
|
981
|
-
|
982
|
-
|
983
|
-
|
1334
|
+
protected baseUrl: string,
|
1335
|
+
protected fetchFn: typeof fetch = fetch,
|
1336
|
+
protected auth?: AuthConfig
|
984
1337
|
) {}
|
985
1338
|
|
986
|
-
|
1339
|
+
protected async authHeaders(): Promise<HeaderMap> {
|
987
1340
|
if (!this.auth) return {};
|
988
1341
|
if (typeof this.auth === "function") {
|
989
1342
|
const h = await this.auth();
|
@@ -1009,129 +1362,91 @@ export class ${Type}Client {
|
|
1009
1362
|
return out;
|
1010
1363
|
}
|
1011
1364
|
|
1012
|
-
|
1365
|
+
protected async headers(json = false): Promise<HeaderMap> {
|
1013
1366
|
const extra = await this.authHeaders();
|
1014
1367
|
return json ? { "Content-Type": "application/json", ...extra } : extra;
|
1015
1368
|
}
|
1016
1369
|
|
1017
|
-
|
1370
|
+
protected async okOrThrow(res: Response, action: string, entity: string): Promise<void> {
|
1018
1371
|
if (!res.ok) {
|
1019
1372
|
let detail = "";
|
1020
1373
|
try { detail = await res.text(); } catch {}
|
1021
|
-
throw new Error(\`\${action}
|
1374
|
+
throw new Error(\`\${action} \${entity} failed: \${res.status} \${detail}\`);
|
1022
1375
|
}
|
1023
1376
|
}
|
1024
1377
|
|
1025
|
-
|
1026
|
-
|
1378
|
+
/**
|
1379
|
+
* Make a POST request
|
1380
|
+
*/
|
1381
|
+
protected async post<T>(path: string, body?: any): Promise<T> {
|
1382
|
+
const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
|
1027
1383
|
method: "POST",
|
1028
1384
|
headers: await this.headers(true),
|
1029
|
-
body: JSON.stringify(
|
1385
|
+
body: JSON.stringify(body),
|
1030
1386
|
});
|
1031
|
-
|
1032
|
-
|
1387
|
+
|
1388
|
+
// Handle 404 specially for operations that might return null
|
1389
|
+
if (res.status === 404) {
|
1390
|
+
return null as T;
|
1391
|
+
}
|
1392
|
+
|
1393
|
+
await this.okOrThrow(res, "POST", path);
|
1394
|
+
return (await res.json()) as T;
|
1033
1395
|
}
|
1034
1396
|
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1397
|
+
/**
|
1398
|
+
* Make a GET request
|
1399
|
+
*/
|
1400
|
+
protected async get<T>(path: string): Promise<T> {
|
1401
|
+
const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
|
1038
1402
|
headers: await this.headers(),
|
1039
1403
|
});
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
method: "POST",
|
1048
|
-
headers: await this.headers(true),
|
1049
|
-
body: JSON.stringify(params ?? {}),
|
1050
|
-
});
|
1051
|
-
await this.okOrThrow(res, "list");
|
1052
|
-
return (await res.json()) as Select${Type}[];
|
1404
|
+
|
1405
|
+
if (res.status === 404) {
|
1406
|
+
return null as T;
|
1407
|
+
}
|
1408
|
+
|
1409
|
+
await this.okOrThrow(res, "GET", path);
|
1410
|
+
return (await res.json()) as T;
|
1053
1411
|
}
|
1054
1412
|
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1413
|
+
/**
|
1414
|
+
* Make a PATCH request
|
1415
|
+
*/
|
1416
|
+
protected async patch<T>(path: string, body?: any): Promise<T> {
|
1417
|
+
const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
|
1058
1418
|
method: "PATCH",
|
1059
1419
|
headers: await this.headers(true),
|
1060
|
-
body: JSON.stringify(
|
1420
|
+
body: JSON.stringify(body),
|
1061
1421
|
});
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1422
|
+
|
1423
|
+
if (res.status === 404) {
|
1424
|
+
return null as T;
|
1425
|
+
}
|
1426
|
+
|
1427
|
+
await this.okOrThrow(res, "PATCH", path);
|
1428
|
+
return (await res.json()) as T;
|
1065
1429
|
}
|
1066
1430
|
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1431
|
+
/**
|
1432
|
+
* Make a DELETE request
|
1433
|
+
*/
|
1434
|
+
protected async del<T>(path: string): Promise<T> {
|
1435
|
+
const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
|
1070
1436
|
method: "DELETE",
|
1071
1437
|
headers: await this.headers(),
|
1072
1438
|
});
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1439
|
+
|
1440
|
+
if (res.status === 404) {
|
1441
|
+
return null as T;
|
1442
|
+
}
|
1443
|
+
|
1444
|
+
await this.okOrThrow(res, "DELETE", path);
|
1445
|
+
return (await res.json()) as T;
|
1076
1446
|
}
|
1077
1447
|
}
|
1078
1448
|
`;
|
1079
1449
|
}
|
1080
|
-
function emitClientIndex(tables) {
|
1081
|
-
let out = `/* Generated. Do not edit. */
|
1082
|
-
`;
|
1083
|
-
for (const t of tables) {
|
1084
|
-
out += `import { ${pascal(t.name)}Client } from "./${t.name}";
|
1085
|
-
`;
|
1086
|
-
}
|
1087
|
-
out += `
|
1088
|
-
export type SDKAuthHeadersProvider = () => Promise<Record<string,string>> | Record<string,string>;
|
1089
|
-
`;
|
1090
|
-
out += `export type SDKAuth =
|
1091
|
-
`;
|
1092
|
-
out += ` | SDKAuthHeadersProvider
|
1093
|
-
`;
|
1094
|
-
out += ` | {
|
1095
|
-
`;
|
1096
|
-
out += ` apiKey?: string;
|
1097
|
-
`;
|
1098
|
-
out += ` /** defaults to "x-api-key" */
|
1099
|
-
`;
|
1100
|
-
out += ` apiKeyHeader?: string;
|
1101
|
-
`;
|
1102
|
-
out += ` jwt?: string | (() => Promise<string>);
|
1103
|
-
`;
|
1104
|
-
out += ` headers?: SDKAuthHeadersProvider;
|
1105
|
-
`;
|
1106
|
-
out += ` };
|
1107
|
-
|
1108
|
-
`;
|
1109
|
-
out += `export class SDK {
|
1110
|
-
`;
|
1111
|
-
for (const t of tables) {
|
1112
|
-
out += ` public ${t.name}: ${pascal(t.name)}Client;
|
1113
|
-
`;
|
1114
|
-
}
|
1115
|
-
out += `
|
1116
|
-
constructor(cfg: { baseUrl: string; fetch?: typeof fetch; auth?: SDKAuth }) {
|
1117
|
-
`;
|
1118
|
-
out += ` const f = cfg.fetch ?? fetch;
|
1119
|
-
`;
|
1120
|
-
for (const t of tables) {
|
1121
|
-
out += ` this.${t.name} = new ${pascal(t.name)}Client(cfg.baseUrl, f, cfg.auth);
|
1122
|
-
`;
|
1123
|
-
}
|
1124
|
-
out += ` }
|
1125
|
-
`;
|
1126
|
-
out += `}
|
1127
|
-
`;
|
1128
|
-
for (const t of tables)
|
1129
|
-
out += `export { ${pascal(t.name)}Client } from "./${t.name}";
|
1130
|
-
`;
|
1131
|
-
out += `export * from "./include-spec";
|
1132
|
-
`;
|
1133
|
-
return out;
|
1134
|
-
}
|
1135
1450
|
|
1136
1451
|
// src/emit-include-loader.ts
|
1137
1452
|
function emitIncludeLoader(graph, model, maxDepth) {
|
@@ -1679,6 +1994,7 @@ function emitRouter(tables, hasAuth) {
|
|
1679
1994
|
`);
|
1680
1995
|
return `/* Generated. Do not edit. */
|
1681
1996
|
import { Hono } from "hono";
|
1997
|
+
import { SDK_MANIFEST } from "./sdk-bundle";
|
1682
1998
|
${imports}
|
1683
1999
|
${hasAuth ? `export { authMiddleware } from "./auth";` : ""}
|
1684
2000
|
|
@@ -1706,7 +2022,34 @@ export function createRouter(
|
|
1706
2022
|
deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> } }
|
1707
2023
|
): Hono {
|
1708
2024
|
const router = new Hono();
|
2025
|
+
|
2026
|
+
// Register table routes
|
1709
2027
|
${registrations}
|
2028
|
+
|
2029
|
+
// SDK distribution endpoints
|
2030
|
+
router.get("/sdk/manifest", (c) => {
|
2031
|
+
return c.json({
|
2032
|
+
version: SDK_MANIFEST.version,
|
2033
|
+
generated: SDK_MANIFEST.generated,
|
2034
|
+
files: Object.keys(SDK_MANIFEST.files)
|
2035
|
+
});
|
2036
|
+
});
|
2037
|
+
|
2038
|
+
router.get("/sdk/download", (c) => {
|
2039
|
+
return c.json(SDK_MANIFEST);
|
2040
|
+
});
|
2041
|
+
|
2042
|
+
router.get("/sdk/files/:path{.*}", (c) => {
|
2043
|
+
const path = c.req.param("path");
|
2044
|
+
const content = SDK_MANIFEST.files[path];
|
2045
|
+
if (!content) {
|
2046
|
+
return c.text("File not found", 404);
|
2047
|
+
}
|
2048
|
+
return c.text(content, 200, {
|
2049
|
+
"Content-Type": "text/plain; charset=utf-8"
|
2050
|
+
});
|
2051
|
+
});
|
2052
|
+
|
1710
2053
|
return router;
|
1711
2054
|
}
|
1712
2055
|
|
@@ -1740,6 +2083,29 @@ export * from "./include-spec";
|
|
1740
2083
|
`;
|
1741
2084
|
}
|
1742
2085
|
|
2086
|
+
// src/emit-sdk-bundle.ts
|
2087
|
+
function emitSdkBundle(clientFiles) {
|
2088
|
+
const files = {};
|
2089
|
+
for (const file of clientFiles) {
|
2090
|
+
const parts = file.path.split("/");
|
2091
|
+
const clientIndex = parts.lastIndexOf("client");
|
2092
|
+
if (clientIndex >= 0 && clientIndex < parts.length - 1) {
|
2093
|
+
const relativePath = parts.slice(clientIndex + 1).join("/");
|
2094
|
+
files[relativePath] = file.content;
|
2095
|
+
}
|
2096
|
+
}
|
2097
|
+
const version = `1.0.0`;
|
2098
|
+
const generated = new Date().toISOString();
|
2099
|
+
return `/* Generated. Do not edit. */
|
2100
|
+
|
2101
|
+
export const SDK_MANIFEST = {
|
2102
|
+
version: "${version}",
|
2103
|
+
generated: "${generated}",
|
2104
|
+
files: ${JSON.stringify(files, null, 2)}
|
2105
|
+
};
|
2106
|
+
`;
|
2107
|
+
}
|
2108
|
+
|
1743
2109
|
// src/types.ts
|
1744
2110
|
function normalizeAuthConfig(input) {
|
1745
2111
|
if (!input)
|
@@ -1804,6 +2170,7 @@ async function generate(configPath) {
|
|
1804
2170
|
const includeSpec = emitIncludeSpec(graph);
|
1805
2171
|
files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
|
1806
2172
|
files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
|
2173
|
+
files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
|
1807
2174
|
files.push({
|
1808
2175
|
path: join(serverDir, "include-builder.ts"),
|
1809
2176
|
content: emitIncludeBuilder(graph, cfg.includeDepthLimit || 3)
|
@@ -1845,6 +2212,11 @@ async function generate(configPath) {
|
|
1845
2212
|
path: join(serverDir, "router.ts"),
|
1846
2213
|
content: emitRouter(Object.values(model.tables), !!normalizedAuth?.strategy && normalizedAuth.strategy !== "none")
|
1847
2214
|
});
|
2215
|
+
const clientFiles = files.filter((f) => f.path.includes(clientDir));
|
2216
|
+
files.push({
|
2217
|
+
path: join(serverDir, "sdk-bundle.ts"),
|
2218
|
+
content: emitSdkBundle(clientFiles)
|
2219
|
+
});
|
1848
2220
|
console.log("✍️ Writing files...");
|
1849
2221
|
await writeFiles(files);
|
1850
2222
|
console.log(`✅ Generated ${files.length} files`);
|
@@ -1854,41 +2226,75 @@ async function generate(configPath) {
|
|
1854
2226
|
|
1855
2227
|
// src/cli.ts
|
1856
2228
|
var import_config2 = __toESM(require_config(), 1);
|
1857
|
-
import { resolve } from "node:path";
|
2229
|
+
import { resolve as resolve3 } from "node:path";
|
1858
2230
|
import { readFileSync } from "node:fs";
|
1859
2231
|
import { fileURLToPath } from "node:url";
|
1860
|
-
import { dirname as
|
2232
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
1861
2233
|
var __filename2 = fileURLToPath(import.meta.url);
|
1862
|
-
var __dirname2 =
|
1863
|
-
var packageJson = JSON.parse(readFileSync(
|
2234
|
+
var __dirname2 = dirname3(__filename2);
|
2235
|
+
var packageJson = JSON.parse(readFileSync(join3(__dirname2, "../package.json"), "utf-8"));
|
1864
2236
|
var VERSION = packageJson.version;
|
1865
2237
|
var args = process.argv.slice(2);
|
1866
|
-
|
2238
|
+
var command = args[0];
|
2239
|
+
if (args.includes("--version") || args.includes("-v") || command === "version") {
|
1867
2240
|
console.log(`postgresdk v${VERSION}`);
|
1868
2241
|
process.exit(0);
|
1869
2242
|
}
|
1870
|
-
if (args.includes("--help") || args.includes("-h")) {
|
2243
|
+
if (args.includes("--help") || args.includes("-h") || command === "help" || !command) {
|
1871
2244
|
console.log(`
|
1872
2245
|
postgresdk - Generate typed SDK from PostgreSQL
|
1873
2246
|
|
1874
2247
|
Usage:
|
1875
|
-
postgresdk [options]
|
2248
|
+
postgresdk <command> [options]
|
2249
|
+
|
2250
|
+
Commands:
|
2251
|
+
init Create a postgresdk.config.ts file
|
2252
|
+
generate Generate SDK from database
|
2253
|
+
pull Pull SDK from API endpoint
|
2254
|
+
version Show version
|
2255
|
+
help Show help
|
1876
2256
|
|
1877
|
-
Options:
|
2257
|
+
Init Options:
|
2258
|
+
(no options)
|
2259
|
+
|
2260
|
+
Generate Options:
|
1878
2261
|
-c, --config <path> Path to config file (default: postgresdk.config.ts)
|
1879
|
-
|
1880
|
-
|
2262
|
+
|
2263
|
+
Pull Options:
|
2264
|
+
--from <url> API URL to pull SDK from
|
2265
|
+
--output <path> Output directory (default: ./src/sdk)
|
2266
|
+
--token <token> Authentication token
|
2267
|
+
-c, --config <path> Path to config file with pull settings
|
2268
|
+
|
2269
|
+
Examples:
|
2270
|
+
postgresdk init # Create config file
|
2271
|
+
postgresdk generate # Generate using postgresdk.config.ts
|
2272
|
+
postgresdk generate -c custom.config.ts
|
2273
|
+
postgresdk pull --from=https://api.com --output=./src/sdk
|
2274
|
+
postgresdk pull # Pull using config file
|
1881
2275
|
`);
|
1882
2276
|
process.exit(0);
|
1883
2277
|
}
|
1884
|
-
|
1885
|
-
|
1886
|
-
|
1887
|
-
|
1888
|
-
|
1889
|
-
|
1890
|
-
|
1891
|
-
|
1892
|
-
|
2278
|
+
if (command === "init") {
|
2279
|
+
const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_cli_init(), exports_cli_init));
|
2280
|
+
await initCommand2(args.slice(1));
|
2281
|
+
} else if (command === "generate") {
|
2282
|
+
let configPath = "postgresdk.config.ts";
|
2283
|
+
const configIndex = args.findIndex((a) => a === "-c" || a === "--config");
|
2284
|
+
if (configIndex !== -1 && args[configIndex + 1]) {
|
2285
|
+
configPath = args[configIndex + 1];
|
2286
|
+
}
|
2287
|
+
try {
|
2288
|
+
await generate(resolve3(process.cwd(), configPath));
|
2289
|
+
} catch (err) {
|
2290
|
+
console.error("❌ Generation failed:", err);
|
2291
|
+
process.exit(1);
|
2292
|
+
}
|
2293
|
+
} else if (command === "pull") {
|
2294
|
+
const { pullCommand: pullCommand2 } = await Promise.resolve().then(() => (init_cli_pull(), exports_cli_pull));
|
2295
|
+
await pullCommand2(args.slice(1));
|
2296
|
+
} else {
|
2297
|
+
console.error(`❌ Unknown command: ${command}`);
|
2298
|
+
console.error(`Run 'postgresdk help' for usage information`);
|
1893
2299
|
process.exit(1);
|
1894
2300
|
}
|