@authend/sdk 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/bin/authend-gen.mjs +253 -0
- package/dist/client.d.ts +17944 -0
- package/dist/client.js +144 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.js +1 -0
- package/package.json +29 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
|
|
7
|
+
function parseArgs(argv) {
|
|
8
|
+
const [command = "generate", ...rest] = argv;
|
|
9
|
+
const options = {};
|
|
10
|
+
|
|
11
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
12
|
+
const token = rest[index];
|
|
13
|
+
if (!token.startsWith("--")) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const key = token.slice(2);
|
|
17
|
+
const value = rest[index + 1] && !rest[index + 1].startsWith("--") ? rest[++index] : "true";
|
|
18
|
+
options[key] = value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return { command, options };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function readJson(filePath) {
|
|
25
|
+
try {
|
|
26
|
+
const contents = await readFile(filePath, "utf8");
|
|
27
|
+
return JSON.parse(contents);
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function loadConfig(cwd) {
|
|
34
|
+
const fileConfig = (await readJson(resolve(cwd, "authend.config.json"))) ?? {};
|
|
35
|
+
const packageJson = (await readJson(resolve(cwd, "package.json"))) ?? {};
|
|
36
|
+
const packageConfig = packageJson.authend ?? {};
|
|
37
|
+
return {
|
|
38
|
+
...fileConfig,
|
|
39
|
+
...packageConfig,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ensureTrailingApiUrl(url) {
|
|
44
|
+
return url.replace(/\/+$/, "");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function pascalCase(value) {
|
|
48
|
+
return value
|
|
49
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
50
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
51
|
+
.filter(Boolean)
|
|
52
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
53
|
+
.join("");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function typeForField(field) {
|
|
57
|
+
let baseType = "string";
|
|
58
|
+
switch (field.type) {
|
|
59
|
+
case "boolean":
|
|
60
|
+
baseType = "boolean";
|
|
61
|
+
break;
|
|
62
|
+
case "integer":
|
|
63
|
+
case "bigint":
|
|
64
|
+
baseType = "number";
|
|
65
|
+
break;
|
|
66
|
+
case "numeric":
|
|
67
|
+
baseType = "string";
|
|
68
|
+
break;
|
|
69
|
+
case "jsonb":
|
|
70
|
+
baseType = "unknown";
|
|
71
|
+
break;
|
|
72
|
+
case "enum":
|
|
73
|
+
baseType =
|
|
74
|
+
Array.isArray(field.enumValues) && field.enumValues.length > 0
|
|
75
|
+
? field.enumValues.map((value) => JSON.stringify(value)).join(" | ")
|
|
76
|
+
: "string";
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
baseType = "string";
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return field.nullable ? `${baseType} | null` : baseType;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function renderFieldsShape(fields, { partial = false } = {}) {
|
|
87
|
+
if (!fields.length) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return [
|
|
92
|
+
"{",
|
|
93
|
+
...fields.map((field) => {
|
|
94
|
+
const optional = partial || field.nullable || field.default !== null && field.default !== undefined;
|
|
95
|
+
return ` ${field.name}${optional ? "?" : ""}: ${typeForField(field)};`;
|
|
96
|
+
}),
|
|
97
|
+
"}",
|
|
98
|
+
].join("\n");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function renderListParams(resource) {
|
|
102
|
+
const sortUnion = resource.sortFields.length > 0 ? resource.sortFields.map((value) => JSON.stringify(value)).join(" | ") : "never";
|
|
103
|
+
const filterUnion = resource.filterFields.length > 0 ? resource.filterFields.map((value) => JSON.stringify(value)).join(" | ") : "never";
|
|
104
|
+
const includeUnion = resource.includeFields.length > 0 ? resource.includeFields.map((value) => JSON.stringify(value)).join(" | ") : "never";
|
|
105
|
+
|
|
106
|
+
return [
|
|
107
|
+
"Omit<ResourceListParams, \"sort\" | \"filterField\" | \"include\"> & {",
|
|
108
|
+
` sort?: ${sortUnion};`,
|
|
109
|
+
` filterField?: ${filterUnion};`,
|
|
110
|
+
` include?: ${includeUnion} | Array<${includeUnion}>;`,
|
|
111
|
+
"}",
|
|
112
|
+
].join("\n");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function renderOperations(resource) {
|
|
116
|
+
return [
|
|
117
|
+
"{",
|
|
118
|
+
` list: ${resource.operations.list ? "true" : "false"};`,
|
|
119
|
+
` get: ${resource.operations.get ? "true" : "false"};`,
|
|
120
|
+
` create: ${resource.operations.create ? "true" : "false"};`,
|
|
121
|
+
` update: ${resource.operations.update ? "true" : "false"};`,
|
|
122
|
+
` delete: ${resource.operations.delete ? "true" : "false"};`,
|
|
123
|
+
"}",
|
|
124
|
+
].join("\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function generateSource(manifest, apiUrl) {
|
|
128
|
+
const blocks = [
|
|
129
|
+
"/* eslint-disable */",
|
|
130
|
+
`// Generated by authend-gen from ${apiUrl}/api/system/sdk-schema`,
|
|
131
|
+
`// Generated at ${manifest.generatedAt}`,
|
|
132
|
+
'import type { AuthendSchemaResource, AuthendSchemaRuntime, ResourceListParams } from "@authend/sdk";',
|
|
133
|
+
"",
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
for (const resource of manifest.resources) {
|
|
137
|
+
const typeBase = pascalCase(resource.key);
|
|
138
|
+
const recordShape = renderFieldsShape(resource.fields);
|
|
139
|
+
const createShape = resource.operations.create ? renderFieldsShape(resource.createFields) : null;
|
|
140
|
+
const updateShape = resource.operations.update ? renderFieldsShape(resource.updateFields, { partial: true }) : null;
|
|
141
|
+
|
|
142
|
+
blocks.push(`export interface ${typeBase}Record ${recordShape ?? "{}"}`);
|
|
143
|
+
blocks.push("");
|
|
144
|
+
blocks.push(`export type ${typeBase}CreateInput = ${createShape ?? "never"};`);
|
|
145
|
+
blocks.push("");
|
|
146
|
+
blocks.push(`export type ${typeBase}UpdateInput = ${updateShape ?? "never"};`);
|
|
147
|
+
blocks.push("");
|
|
148
|
+
blocks.push(`export type ${typeBase}ListParams = ${renderListParams(resource)};`);
|
|
149
|
+
blocks.push("");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
blocks.push("export interface AuthendGeneratedSchema {");
|
|
153
|
+
blocks.push(" resources: {");
|
|
154
|
+
for (const resource of manifest.resources) {
|
|
155
|
+
const typeBase = pascalCase(resource.key);
|
|
156
|
+
blocks.push(` ${resource.key}: AuthendSchemaResource<${typeBase}Record, ${typeBase}CreateInput, ${typeBase}UpdateInput, ${typeBase}ListParams, ${renderOperations(resource)}>;`);
|
|
157
|
+
}
|
|
158
|
+
blocks.push(" };");
|
|
159
|
+
blocks.push("}");
|
|
160
|
+
blocks.push("");
|
|
161
|
+
blocks.push("export const authendSchema = {");
|
|
162
|
+
blocks.push(" resources: {");
|
|
163
|
+
for (const resource of manifest.resources) {
|
|
164
|
+
blocks.push(` ${resource.key}: {`);
|
|
165
|
+
blocks.push(` routeSegment: ${JSON.stringify(resource.routeSegment)},`);
|
|
166
|
+
blocks.push(" operations: {");
|
|
167
|
+
blocks.push(` list: ${resource.operations.list ? "true" : "false"},`);
|
|
168
|
+
blocks.push(` get: ${resource.operations.get ? "true" : "false"},`);
|
|
169
|
+
blocks.push(` create: ${resource.operations.create ? "true" : "false"},`);
|
|
170
|
+
blocks.push(` update: ${resource.operations.update ? "true" : "false"},`);
|
|
171
|
+
blocks.push(` delete: ${resource.operations.delete ? "true" : "false"},`);
|
|
172
|
+
blocks.push(" },");
|
|
173
|
+
blocks.push(" },");
|
|
174
|
+
}
|
|
175
|
+
blocks.push(" },");
|
|
176
|
+
blocks.push("} satisfies AuthendSchemaRuntime<AuthendGeneratedSchema>;");
|
|
177
|
+
blocks.push("");
|
|
178
|
+
blocks.push("export type AuthendSchema = AuthendGeneratedSchema;");
|
|
179
|
+
blocks.push("");
|
|
180
|
+
|
|
181
|
+
return blocks.join("\n");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function writeFileRecursive(filePath, contents) {
|
|
185
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
186
|
+
await writeFile(filePath, contents, "utf8");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function commandInit(cwd) {
|
|
190
|
+
const configPath = resolve(cwd, "authend.config.json");
|
|
191
|
+
const existing = await readJson(configPath);
|
|
192
|
+
if (existing) {
|
|
193
|
+
process.stdout.write(`Config already exists at ${configPath}\n`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const config = {
|
|
198
|
+
apiUrl: "http://localhost:3000",
|
|
199
|
+
output: "./src/generated/authend.ts",
|
|
200
|
+
};
|
|
201
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
202
|
+
process.stdout.write(`Created ${configPath}\n`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function commandGenerate(cwd, options) {
|
|
206
|
+
const config = await loadConfig(cwd);
|
|
207
|
+
const apiUrl = ensureTrailingApiUrl(options["api-url"] ?? options.url ?? config.apiUrl ?? "");
|
|
208
|
+
const output = options.output ?? config.output ?? "./src/generated/authend.ts";
|
|
209
|
+
const schemaOutput = options["schema-output"] ?? config.schemaOutput ?? null;
|
|
210
|
+
|
|
211
|
+
if (!apiUrl) {
|
|
212
|
+
throw new Error("Missing apiUrl. Pass --api-url or create authend.config.json.");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const response = await fetch(`${apiUrl}/api/system/sdk-schema`);
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
throw new Error(`Failed to fetch SDK schema from ${apiUrl}/api/system/sdk-schema: ${response.status} ${response.statusText}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const manifest = await response.json();
|
|
221
|
+
const outputPath = isAbsolute(output) ? output : resolve(cwd, output);
|
|
222
|
+
const source = generateSource(manifest, apiUrl);
|
|
223
|
+
await writeFileRecursive(outputPath, source);
|
|
224
|
+
|
|
225
|
+
if (schemaOutput) {
|
|
226
|
+
const schemaPath = isAbsolute(schemaOutput) ? schemaOutput : resolve(cwd, schemaOutput);
|
|
227
|
+
await writeFileRecursive(schemaPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
process.stdout.write(`Generated Authend types at ${outputPath}\n`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function main() {
|
|
234
|
+
const cwd = process.cwd();
|
|
235
|
+
const { command, options } = parseArgs(process.argv.slice(2));
|
|
236
|
+
|
|
237
|
+
if (command === "init") {
|
|
238
|
+
await commandInit(cwd);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (command === "generate") {
|
|
243
|
+
await commandGenerate(cwd, options);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
throw new Error(`Unknown command: ${command}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
main().catch((error) => {
|
|
251
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
252
|
+
process.exit(1);
|
|
253
|
+
});
|