@hyperscale0/sdk 1.0.338 → 1.0.340

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,162 @@
1
+ import { createClient } from "./client.js";
2
+
3
+ const usage = `Usage: npx @hyperscale0/sdk types --product <prd_id> [--name ProductName]
4
+ Reads HYPERSCALE_API_KEY, plus HYPERSCALE_BASE_URL and HYPERSCALE_ENVIRONMENT when set.`;
5
+
6
+ // Code-unit order, so the output does not depend on the machine's locale.
7
+ const byName = ([left], [right]) => (left < right ? -1 : left > right ? 1 : 0);
8
+
9
+ // Stands in for a schema discovery left out, so the type still fits ProductTypes.
10
+ const anyRecord = { type: "object" };
11
+
12
+ const union = (members) => [...new Set(members)].join(" | ") || "never";
13
+
14
+ const comment = (schema, indent) =>
15
+ typeof schema?.description === "string"
16
+ ? `${indent}/** ${schema.description.replace(/\s+/g, " ").replaceAll("*/", "*\\/")} */\n`
17
+ : "";
18
+
19
+ /** One JSON Schema as a TypeScript type. Keywords it does not map read as unknown. */
20
+ function typeOf(schema, indent) {
21
+ if (typeof schema !== "object" || schema === null) return "unknown";
22
+ if ("const" in schema) return JSON.stringify(schema.const);
23
+ if (Array.isArray(schema.enum))
24
+ return union(schema.enum.map((value) => JSON.stringify(value)));
25
+ const variants = schema.oneOf ?? schema.anyOf;
26
+ if (Array.isArray(variants))
27
+ return union(variants.map((variant) => typeOf(variant, indent)));
28
+ if (Array.isArray(schema.type))
29
+ return union(
30
+ schema.type.map((type) => typeOf({ ...schema, type }, indent)),
31
+ );
32
+ switch (schema.type) {
33
+ case "string":
34
+ return "string";
35
+ case "number":
36
+ case "integer":
37
+ return "number";
38
+ case "boolean":
39
+ return "boolean";
40
+ case "null":
41
+ return "null";
42
+ case "array":
43
+ return `ReadonlyArray<${typeOf(schema.items, indent)}>`;
44
+ case "object":
45
+ return objectType(schema, indent);
46
+ default:
47
+ return "unknown";
48
+ }
49
+ }
50
+
51
+ function objectType(schema, indent) {
52
+ const inner = `${indent} `;
53
+ const properties = Object.entries(schema.properties ?? {}).sort(byName);
54
+ const required = new Set(schema.required ?? []);
55
+ const extra = schema.additionalProperties;
56
+ const lines = properties.map(
57
+ ([name, property]) =>
58
+ `${comment(property, inner)}${inner}readonly ${JSON.stringify(name)}${required.has(name) ? "" : "?"}: ${typeOf(property, inner)};`,
59
+ );
60
+ if (extra === false && properties.length === 0)
61
+ return "{ readonly [key: string]: never }";
62
+ // A named property's type must fit the index signature, so beside named
63
+ // properties the extra values read as unknown.
64
+ if (extra !== false && (properties.length === 0 || extra !== undefined))
65
+ lines.push(
66
+ `${inner}readonly [key: string]: ${properties.length === 0 ? typeOf(extra, inner) : "unknown"};`,
67
+ );
68
+ return `{\n${lines.join("\n")}\n${indent}}`;
69
+ }
70
+
71
+ /** A key and how to render its type at an indent, with an optional doc line. */
72
+ function block(entries, indent) {
73
+ if (entries.length === 0) return "{}";
74
+ const inner = `${indent} `;
75
+ const lines = entries
76
+ .sort(byName)
77
+ .map(
78
+ ([key, render, description]) =>
79
+ `${comment({ description }, inner)}${inner}${key}: ${render(inner)};`,
80
+ );
81
+ return `{\n${lines.join("\n")}\n${indent}}`;
82
+ }
83
+
84
+ function kindType(kind, indent) {
85
+ // Node 20 has no Map.groupBy.
86
+ const variants = new Map();
87
+ for (const action of kind.actions)
88
+ variants.set(action.name, [...(variants.get(action.name) ?? []), action]);
89
+ const actions = [...variants].map(([name, entries]) => [
90
+ JSON.stringify(name),
91
+ (inner) =>
92
+ union(
93
+ entries.map((action) =>
94
+ block(
95
+ [
96
+ ["fields", (at) => typeOf(action.fieldsSchema ?? anyRecord, at)],
97
+ ["input", (at) => typeOf(action.inputSchema ?? anyRecord, at)],
98
+ ],
99
+ inner,
100
+ ),
101
+ ),
102
+ ),
103
+ ]);
104
+ return block(
105
+ [
106
+ [
107
+ "fields",
108
+ (inner) =>
109
+ typeOf(kind.createSchema?.properties?.fields ?? anyRecord, inner),
110
+ ],
111
+ ["actions", (inner) => block(actions, inner)],
112
+ ],
113
+ indent,
114
+ );
115
+ }
116
+
117
+ /**
118
+ * The declaration source for one Product, from its product.objects.discover
119
+ * output. The same discovery gives the same bytes, so a regenerated file
120
+ * diffs only where the Product changed.
121
+ */
122
+ export function productTypes(discovery, name = "Product") {
123
+ if (!/^[A-Za-z_$][\w$]*$/.test(name))
124
+ throw new Error(`${name} is not a TypeScript type name`);
125
+ const kinds = discovery.kinds.map((kind) => [
126
+ JSON.stringify(kind.kind),
127
+ (inner) => kindType(kind, inner),
128
+ kind.title,
129
+ ]);
130
+ return `// Generated by \`npx @hyperscale0/sdk types\` from product.objects.discover.
131
+ // Regenerate after each publish instead of editing it.
132
+ export interface ${name} ${block([["kinds", (inner) => block(kinds, inner)]], "")}
133
+ `;
134
+ }
135
+
136
+ /** `types --product <prd_id> [--name ProductName]`: the declaration, or an Error. */
137
+ export async function typesCommand(args, env) {
138
+ const [command, ...rest] = args;
139
+ const flags = {};
140
+ for (let index = 0; index < rest.length; index += 2) {
141
+ const flag = rest[index];
142
+ const value = rest[index + 1];
143
+ if (!["--product", "--name"].includes(flag) || value === undefined)
144
+ throw new Error(usage);
145
+ flags[flag.slice(2)] = value;
146
+ }
147
+ const productId = flags.product ?? env.HYPERSCALE_PRODUCT_ID;
148
+ if (command !== "types" || !productId) throw new Error(usage);
149
+ if (!env.HYPERSCALE_API_KEY)
150
+ throw new Error(
151
+ "Set HYPERSCALE_API_KEY to a key from `hyperscale key create --product <id>`.",
152
+ );
153
+ const client = createClient({
154
+ apiKey: env.HYPERSCALE_API_KEY,
155
+ productId,
156
+ ...(env.HYPERSCALE_BASE_URL ? { baseUrl: env.HYPERSCALE_BASE_URL } : {}),
157
+ ...(env.HYPERSCALE_ENVIRONMENT
158
+ ? { environment: env.HYPERSCALE_ENVIRONMENT }
159
+ : {}),
160
+ });
161
+ return productTypes(await client.discover(), flags.name);
162
+ }