@pylo/node 0.0.4 → 0.0.6
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/cli.js +457 -1
- package/dist/codegen.js +5 -2
- package/dist/index.d.ts +166 -3
- package/dist/index.js +368 -2
- package/package.json +5 -3
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,463 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// ../core/dist/codegen/index.js
|
|
4
|
+
import { resolve as resolve3 } from "path";
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import { createJiti } from "jiti";
|
|
8
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
9
|
+
import { resolve as resolve2 } from "path";
|
|
10
|
+
var DEFAULT_ENDPOINT = "https://api.pyloapp.com/graphql";
|
|
11
|
+
async function loadConfig(cwd) {
|
|
12
|
+
const candidates = ["pylo.config.ts", "pylo.config.js"];
|
|
13
|
+
for (const envFile of [".env", ".env.local"]) {
|
|
14
|
+
const envPath = resolve(cwd, envFile);
|
|
15
|
+
if (existsSync(envPath)) {
|
|
16
|
+
process.loadEnvFile(envPath);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const jiti = createJiti(cwd);
|
|
20
|
+
for (const filename of candidates) {
|
|
21
|
+
const configPath = resolve(cwd, filename);
|
|
22
|
+
if (existsSync(configPath)) {
|
|
23
|
+
const mod = await jiti.import(configPath);
|
|
24
|
+
if (!mod.default) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`${filename} must have a default export. Use defineConfig() from '@pylo/core/codegen'.`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
const config = mod.default;
|
|
30
|
+
if (!config.apiKey) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Missing 'apiKey' in ${filename}. An API key is required to introspect the Pylo schema.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
endpoint: DEFAULT_ENDPOINT,
|
|
37
|
+
unknownFieldBehavior: "error",
|
|
38
|
+
...config
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
throw new Error(
|
|
43
|
+
"No pylo.config.ts or pylo.config.js found in project root. Create one using defineConfig() from '@pylo/core/codegen'."
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
async function graphqlRequestWithApiKey(endpoint, query, variables, apiKey) {
|
|
47
|
+
const headers = {
|
|
48
|
+
"Content-Type": "application/json"
|
|
49
|
+
};
|
|
50
|
+
if (apiKey) {
|
|
51
|
+
headers["pylo-api-key"] = apiKey;
|
|
52
|
+
}
|
|
53
|
+
const response = await fetch(endpoint, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers,
|
|
56
|
+
body: JSON.stringify({ query, variables })
|
|
57
|
+
});
|
|
58
|
+
return response.json();
|
|
59
|
+
}
|
|
60
|
+
var ENTITY_LIST_QUERY = `
|
|
61
|
+
query PyloSchemaFetch($pagination: PaginationInput) {
|
|
62
|
+
entityList(pagination: $pagination) {
|
|
63
|
+
data {
|
|
64
|
+
name
|
|
65
|
+
shortcode
|
|
66
|
+
is_system_entity
|
|
67
|
+
entity_fields {
|
|
68
|
+
data {
|
|
69
|
+
name
|
|
70
|
+
data_type
|
|
71
|
+
validation_string
|
|
72
|
+
form_type
|
|
73
|
+
default_value
|
|
74
|
+
variant_entity_field {
|
|
75
|
+
data {
|
|
76
|
+
name
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
entity_relations {
|
|
82
|
+
data {
|
|
83
|
+
type
|
|
84
|
+
field_name
|
|
85
|
+
target_field_name
|
|
86
|
+
target_entity {
|
|
87
|
+
data {
|
|
88
|
+
name
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
entity_is_tightly_coupled
|
|
92
|
+
target_entity_is_tightly_coupled
|
|
93
|
+
allow_connect_create
|
|
94
|
+
allow_connect_existing
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
entity_related {
|
|
98
|
+
data {
|
|
99
|
+
type
|
|
100
|
+
field_name
|
|
101
|
+
target_field_name
|
|
102
|
+
entity {
|
|
103
|
+
data {
|
|
104
|
+
name
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
entity_is_tightly_coupled
|
|
108
|
+
target_entity_is_tightly_coupled
|
|
109
|
+
allow_connect_create
|
|
110
|
+
allow_connect_existing
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
pagination {
|
|
115
|
+
total
|
|
116
|
+
has_more_pages
|
|
117
|
+
current_page
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
async function fetchSchema(config) {
|
|
123
|
+
var _a, _b;
|
|
124
|
+
const allEntities = [];
|
|
125
|
+
let page = 1;
|
|
126
|
+
let hasMore = true;
|
|
127
|
+
while (hasMore) {
|
|
128
|
+
const response = await graphqlRequestWithApiKey(
|
|
129
|
+
config.endpoint,
|
|
130
|
+
ENTITY_LIST_QUERY,
|
|
131
|
+
{ pagination: { page, per_page: 50 } },
|
|
132
|
+
config.apiKey
|
|
133
|
+
);
|
|
134
|
+
if (response.errors) {
|
|
135
|
+
const errMsg = Array.isArray(response.errors) ? response.errors.map((e) => e.message).join(", ") : (_b = (_a = response.errors.generalError) == null ? void 0 : _a.message) != null ? _b : "Unknown error";
|
|
136
|
+
throw new Error(`Failed to fetch schema: ${errMsg}`);
|
|
137
|
+
}
|
|
138
|
+
if (!response.data) {
|
|
139
|
+
throw new Error("No data returned from schema fetch");
|
|
140
|
+
}
|
|
141
|
+
const { data, pagination } = response.data.entityList;
|
|
142
|
+
allEntities.push(...data);
|
|
143
|
+
hasMore = pagination.has_more_pages;
|
|
144
|
+
page++;
|
|
145
|
+
}
|
|
146
|
+
return allEntities;
|
|
147
|
+
}
|
|
148
|
+
var DATA_TYPE_MAP = {
|
|
149
|
+
TEXT: "string",
|
|
150
|
+
LONGTEXT: "string",
|
|
151
|
+
RICHTEXT: "string",
|
|
152
|
+
INT: "number",
|
|
153
|
+
FLOAT: "number",
|
|
154
|
+
JSON: "Record<string, unknown> | unknown[]",
|
|
155
|
+
DATE: "string",
|
|
156
|
+
DATETIME: "string",
|
|
157
|
+
TIME: "string",
|
|
158
|
+
BOOLEAN: "boolean"
|
|
159
|
+
};
|
|
160
|
+
function mapDataType(dataType) {
|
|
161
|
+
var _a;
|
|
162
|
+
return (_a = DATA_TYPE_MAP[dataType]) != null ? _a : "string";
|
|
163
|
+
}
|
|
164
|
+
function isNullable(field) {
|
|
165
|
+
return field.validation_string !== "required";
|
|
166
|
+
}
|
|
167
|
+
function classifyRelation(relationType) {
|
|
168
|
+
if (relationType === "ManyToOne" || relationType === "OneToOne") {
|
|
169
|
+
return "hasOne";
|
|
170
|
+
}
|
|
171
|
+
return "hasMany";
|
|
172
|
+
}
|
|
173
|
+
function classifyReverseRelation(relationType) {
|
|
174
|
+
if (relationType === "OneToMany" || relationType === "OneToOne") {
|
|
175
|
+
return "hasOne";
|
|
176
|
+
}
|
|
177
|
+
return "hasMany";
|
|
178
|
+
}
|
|
179
|
+
function getMutationSuffixes(relType) {
|
|
180
|
+
if (relType === "hasOne") {
|
|
181
|
+
return ["_set"];
|
|
182
|
+
}
|
|
183
|
+
return ["_set", "_connect", "_disconnect"];
|
|
184
|
+
}
|
|
185
|
+
function toEntityKey(pascalName) {
|
|
186
|
+
return pascalName.charAt(0).toLowerCase() + pascalName.slice(1);
|
|
187
|
+
}
|
|
188
|
+
function analyzeField(field) {
|
|
189
|
+
var _a, _b;
|
|
190
|
+
const hasVariants = ((_b = (_a = field.variant_entity_field) == null ? void 0 : _a.data) == null ? void 0 : _b.name) != null;
|
|
191
|
+
return {
|
|
192
|
+
name: field.name,
|
|
193
|
+
tsType: mapDataType(field.data_type),
|
|
194
|
+
nullable: isNullable(field),
|
|
195
|
+
variantFieldName: hasVariants ? `${field.name}_variants` : null
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function analyzeRelation(relation) {
|
|
199
|
+
var _a, _b;
|
|
200
|
+
const targetName = (_b = (_a = relation.target_entity) == null ? void 0 : _a.data) == null ? void 0 : _b.name;
|
|
201
|
+
if (!targetName || !relation.field_name) return null;
|
|
202
|
+
const relType = classifyRelation(relation.type);
|
|
203
|
+
return {
|
|
204
|
+
fieldName: relation.field_name,
|
|
205
|
+
type: relType,
|
|
206
|
+
targetEntityKey: toEntityKey(targetName),
|
|
207
|
+
targetEntityPascalName: targetName,
|
|
208
|
+
suffixes: getMutationSuffixes(relType)
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function analyzeReverseRelation(relation) {
|
|
212
|
+
var _a, _b;
|
|
213
|
+
const sourceName = (_b = (_a = relation.entity) == null ? void 0 : _a.data) == null ? void 0 : _b.name;
|
|
214
|
+
if (!sourceName || !relation.target_field_name) return null;
|
|
215
|
+
const relType = classifyReverseRelation(relation.type);
|
|
216
|
+
return {
|
|
217
|
+
fieldName: relation.target_field_name,
|
|
218
|
+
type: relType,
|
|
219
|
+
targetEntityKey: toEntityKey(sourceName),
|
|
220
|
+
targetEntityPascalName: sourceName,
|
|
221
|
+
suffixes: getMutationSuffixes(relType)
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function analyzeEntities(rawEntities) {
|
|
225
|
+
return rawEntities.map((entity) => {
|
|
226
|
+
var _a, _b, _c, _d, _e, _f;
|
|
227
|
+
const fields = ((_b = (_a = entity.entity_fields) == null ? void 0 : _a.data) != null ? _b : []).map(analyzeField);
|
|
228
|
+
const forwardRelations = ((_d = (_c = entity.entity_relations) == null ? void 0 : _c.data) != null ? _d : []).map(analyzeRelation).filter((r) => r !== null);
|
|
229
|
+
const reverseRelations = ((_f = (_e = entity.entity_related) == null ? void 0 : _e.data) != null ? _f : []).map(analyzeReverseRelation).filter((r) => r !== null);
|
|
230
|
+
const seen = new Set(forwardRelations.map((r) => r.fieldName));
|
|
231
|
+
const deduped = reverseRelations.filter((r) => !seen.has(r.fieldName));
|
|
232
|
+
return {
|
|
233
|
+
key: toEntityKey(entity.name),
|
|
234
|
+
pascalName: entity.name,
|
|
235
|
+
shortcode: entity.shortcode,
|
|
236
|
+
isSystem: entity.is_system_entity,
|
|
237
|
+
fields,
|
|
238
|
+
relations: [...forwardRelations, ...deduped]
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
var VARIANT_TYPE = "{ variant: string; value: string }";
|
|
243
|
+
function fieldTypeString(field) {
|
|
244
|
+
if (field.nullable) {
|
|
245
|
+
return `${field.tsType} | null`;
|
|
246
|
+
}
|
|
247
|
+
return field.tsType;
|
|
248
|
+
}
|
|
249
|
+
function indent(text, level) {
|
|
250
|
+
const spaces = " ".repeat(level);
|
|
251
|
+
return text.split("\n").map((line) => line.trim() ? spaces + line : line).join("\n");
|
|
252
|
+
}
|
|
253
|
+
function getVariantFieldNames(entity) {
|
|
254
|
+
return entity.fields.filter((f) => f.variantFieldName !== null).map((f) => f.variantFieldName);
|
|
255
|
+
}
|
|
256
|
+
function generateEntityFieldsType(entity) {
|
|
257
|
+
const lines = entity.fields.map(
|
|
258
|
+
(f) => `${f.name}: ${fieldTypeString(f)};`
|
|
259
|
+
);
|
|
260
|
+
for (const variantName of getVariantFieldNames(entity)) {
|
|
261
|
+
lines.push(`${variantName}: ${VARIANT_TYPE}[] | null;`);
|
|
262
|
+
}
|
|
263
|
+
return lines.join("\n");
|
|
264
|
+
}
|
|
265
|
+
function generateEntityRelationsType(entity) {
|
|
266
|
+
if (entity.relations.length === 0) return "";
|
|
267
|
+
const lines = entity.relations.map(
|
|
268
|
+
(r) => `${r.fieldName}: { type: '${r.type}'; entity: '${r.targetEntityKey}' };`
|
|
269
|
+
);
|
|
270
|
+
return lines.join("\n");
|
|
271
|
+
}
|
|
272
|
+
function generateCreateInputType(entity) {
|
|
273
|
+
const lines = [];
|
|
274
|
+
for (const field of entity.fields) {
|
|
275
|
+
if (field.name === "id" || field.name === "integer_id") continue;
|
|
276
|
+
if (field.name === "created_at" || field.name === "updated_at") continue;
|
|
277
|
+
lines.push(`${field.name}?: ${fieldTypeString(field)};`);
|
|
278
|
+
}
|
|
279
|
+
for (const variantName of getVariantFieldNames(entity)) {
|
|
280
|
+
lines.push(`${variantName}?: ${VARIANT_TYPE}[];`);
|
|
281
|
+
}
|
|
282
|
+
for (const rel of entity.relations) {
|
|
283
|
+
for (const suffix of rel.suffixes) {
|
|
284
|
+
lines.push(
|
|
285
|
+
`${rel.fieldName}${suffix}?: Record<string, unknown>;`
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return lines.join("\n");
|
|
290
|
+
}
|
|
291
|
+
function generateUpdateInputType(entity) {
|
|
292
|
+
const lines = [];
|
|
293
|
+
lines.push("id?: string;");
|
|
294
|
+
lines.push(
|
|
295
|
+
"__search_value?: { field: string; value?: string; not_found_behavior?: 'create' | 'ignore' | 'error'; search_in_all_field_variants?: boolean; multiple_results_allowed?: boolean; multiple_results_use_latest?: boolean };"
|
|
296
|
+
);
|
|
297
|
+
for (const field of entity.fields) {
|
|
298
|
+
if (field.name === "id" || field.name === "integer_id") continue;
|
|
299
|
+
if (field.name === "created_at" || field.name === "updated_at") continue;
|
|
300
|
+
lines.push(`${field.name}?: ${fieldTypeString(field)};`);
|
|
301
|
+
}
|
|
302
|
+
for (const variantName of getVariantFieldNames(entity)) {
|
|
303
|
+
lines.push(`${variantName}?: ${VARIANT_TYPE}[];`);
|
|
304
|
+
}
|
|
305
|
+
for (const rel of entity.relations) {
|
|
306
|
+
for (const suffix of rel.suffixes) {
|
|
307
|
+
lines.push(
|
|
308
|
+
`${rel.fieldName}${suffix}?: Record<string, unknown>;`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return lines.join("\n");
|
|
313
|
+
}
|
|
314
|
+
function generateIndexFile(entities, importSource) {
|
|
315
|
+
const lines = [];
|
|
316
|
+
lines.push(
|
|
317
|
+
"// Auto-generated by @pylo/core codegen \u2014 DO NOT EDIT",
|
|
318
|
+
"",
|
|
319
|
+
"export type {",
|
|
320
|
+
" FilterInput,",
|
|
321
|
+
" PaginationData,",
|
|
322
|
+
" PaginationInput,",
|
|
323
|
+
" QueryInput,",
|
|
324
|
+
" QueryInputCondition,",
|
|
325
|
+
" QueryOperator,",
|
|
326
|
+
" SortInput,",
|
|
327
|
+
" SortOrder,",
|
|
328
|
+
" SearchValueInput,",
|
|
329
|
+
" SchemaMetadata,",
|
|
330
|
+
" EntityMetadata,",
|
|
331
|
+
`} from '${importSource}';`,
|
|
332
|
+
""
|
|
333
|
+
);
|
|
334
|
+
for (const entity of entities) {
|
|
335
|
+
lines.push(`export interface Create${entity.pascalName}Input {`);
|
|
336
|
+
lines.push(indent(generateCreateInputType(entity), 1));
|
|
337
|
+
lines.push("}", "");
|
|
338
|
+
lines.push(`export interface Update${entity.pascalName}Input {`);
|
|
339
|
+
lines.push(indent(generateUpdateInputType(entity), 1));
|
|
340
|
+
lines.push("}", "");
|
|
341
|
+
}
|
|
342
|
+
lines.push("export interface PyloSchema {");
|
|
343
|
+
for (const entity of entities) {
|
|
344
|
+
lines.push(` ${entity.key}: {`);
|
|
345
|
+
lines.push(" fields: {");
|
|
346
|
+
lines.push(indent(generateEntityFieldsType(entity), 3));
|
|
347
|
+
lines.push(" };");
|
|
348
|
+
const relType = generateEntityRelationsType(entity);
|
|
349
|
+
lines.push(" relations: {");
|
|
350
|
+
if (relType) {
|
|
351
|
+
lines.push(indent(relType, 3));
|
|
352
|
+
}
|
|
353
|
+
lines.push(" };");
|
|
354
|
+
lines.push(` createInput: Create${entity.pascalName}Input;`);
|
|
355
|
+
lines.push(` updateInput: Update${entity.pascalName}Input;`);
|
|
356
|
+
lines.push(" };");
|
|
357
|
+
}
|
|
358
|
+
lines.push("}", "");
|
|
359
|
+
return lines.join("\n");
|
|
360
|
+
}
|
|
361
|
+
function generateEntitiesFile(entities, importSource) {
|
|
362
|
+
const lines = [];
|
|
363
|
+
lines.push(
|
|
364
|
+
"// Auto-generated by @pylo/core codegen \u2014 DO NOT EDIT",
|
|
365
|
+
"",
|
|
366
|
+
`import type { PaginationData } from '${importSource}';`,
|
|
367
|
+
""
|
|
368
|
+
);
|
|
369
|
+
for (const entity of entities) {
|
|
370
|
+
lines.push(`export interface ${entity.pascalName} {`);
|
|
371
|
+
for (const field of entity.fields) {
|
|
372
|
+
lines.push(` ${field.name}: ${fieldTypeString(field)};`);
|
|
373
|
+
}
|
|
374
|
+
for (const variantName of getVariantFieldNames(entity)) {
|
|
375
|
+
lines.push(` ${variantName}: ${VARIANT_TYPE}[] | null;`);
|
|
376
|
+
}
|
|
377
|
+
for (const rel of entity.relations) {
|
|
378
|
+
if (rel.type === "hasOne") {
|
|
379
|
+
lines.push(` ${rel.fieldName}?: { data: ${rel.targetEntityPascalName} } | null;`);
|
|
380
|
+
} else {
|
|
381
|
+
lines.push(` ${rel.fieldName}?: { data: ${rel.targetEntityPascalName}[]; pagination: PaginationData };`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
lines.push("}", "");
|
|
385
|
+
}
|
|
386
|
+
return lines.join("\n");
|
|
387
|
+
}
|
|
388
|
+
function generateSchemaMetadataFile(entities, unknownFieldBehavior, importSource) {
|
|
389
|
+
const lines = [];
|
|
390
|
+
lines.push(
|
|
391
|
+
"// Auto-generated by @pylo/core codegen \u2014 DO NOT EDIT",
|
|
392
|
+
"",
|
|
393
|
+
`import type { SchemaMetadata } from '${importSource}';`,
|
|
394
|
+
"",
|
|
395
|
+
"export const schemaMetadata: SchemaMetadata = {",
|
|
396
|
+
` unknownFieldBehavior: '${unknownFieldBehavior}',`,
|
|
397
|
+
" entities: {"
|
|
398
|
+
);
|
|
399
|
+
for (const entity of entities) {
|
|
400
|
+
lines.push(` ${entity.key}: {`);
|
|
401
|
+
lines.push(` pascalName: '${entity.pascalName}',`);
|
|
402
|
+
const scalarNames = entity.fields.map((f) => `'${f.name}'`).join(", ");
|
|
403
|
+
lines.push(` scalarFieldNames: [${scalarNames}],`);
|
|
404
|
+
const variantNames = getVariantFieldNames(entity);
|
|
405
|
+
if (variantNames.length > 0) {
|
|
406
|
+
const variantNamesStr = variantNames.map((n) => `'${n}'`).join(", ");
|
|
407
|
+
lines.push(` variantFieldNames: [${variantNamesStr}],`);
|
|
408
|
+
}
|
|
409
|
+
lines.push(" relations: {");
|
|
410
|
+
for (const rel of entity.relations) {
|
|
411
|
+
lines.push(
|
|
412
|
+
` ${rel.fieldName}: { type: '${rel.type}', entity: '${rel.targetEntityKey}', pascalName: '${rel.targetEntityPascalName}' },`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
lines.push(" },");
|
|
416
|
+
lines.push(" },");
|
|
417
|
+
}
|
|
418
|
+
lines.push(" },", "};", "");
|
|
419
|
+
return lines.join("\n");
|
|
420
|
+
}
|
|
421
|
+
function writeGeneratedFiles(outputDir, files) {
|
|
422
|
+
mkdirSync(outputDir, { recursive: true });
|
|
423
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
424
|
+
const filePath = resolve2(outputDir, filename);
|
|
425
|
+
writeFileSync(filePath, content, "utf-8");
|
|
426
|
+
console.log(` wrote ${filePath}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
async function generate(options) {
|
|
430
|
+
var _a, _b, _c;
|
|
431
|
+
const projectRoot = (_a = options == null ? void 0 : options.cwd) != null ? _a : process.cwd();
|
|
432
|
+
const importSource = (_b = options == null ? void 0 : options.importSource) != null ? _b : "@pylo/core";
|
|
433
|
+
console.log("Loading config...");
|
|
434
|
+
const config = await loadConfig(projectRoot);
|
|
435
|
+
const outputDir = resolve3(projectRoot, (_c = config.output) != null ? _c : ".pylo");
|
|
436
|
+
console.log("Fetching schema from Pylo...");
|
|
437
|
+
const rawEntities = await fetchSchema(config);
|
|
438
|
+
console.log(` found ${rawEntities.length} entities`);
|
|
439
|
+
console.log("Analyzing entities...");
|
|
440
|
+
const entities = analyzeEntities(rawEntities);
|
|
441
|
+
console.log("Generating types...");
|
|
442
|
+
const files = {
|
|
443
|
+
"index.ts": generateIndexFile(entities, importSource),
|
|
444
|
+
"entities.ts": generateEntitiesFile(entities, importSource),
|
|
445
|
+
"schema-metadata.ts": generateSchemaMetadataFile(entities, config.unknownFieldBehavior, importSource)
|
|
446
|
+
};
|
|
447
|
+
console.log(`Writing files to ${outputDir}...`);
|
|
448
|
+
writeGeneratedFiles(outputDir, files);
|
|
449
|
+
console.log(
|
|
450
|
+
`
|
|
451
|
+
Done! Generated types for ${entities.length} entities in ${outputDir}`
|
|
452
|
+
);
|
|
453
|
+
console.log(
|
|
454
|
+
"\nMake sure your tsconfig.json has path aliases set up:"
|
|
455
|
+
);
|
|
456
|
+
console.log(' "@pylo/types": ["./.pylo"]');
|
|
457
|
+
console.log(' "@pylo/types/*": ["./.pylo/*"]');
|
|
458
|
+
}
|
|
459
|
+
|
|
3
460
|
// src/cli.ts
|
|
4
|
-
import { generate } from "@pylo/core/codegen";
|
|
5
461
|
var args = process.argv.slice(2);
|
|
6
462
|
var command = args[0];
|
|
7
463
|
if (command === "generate") {
|
package/dist/codegen.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,168 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
type QueryOperator = "equal" | "notEqual" | "greaterThan" | "lessThan" | "greaterThanOrEqual" | "lessThanOrEqual" | "isNull" | "isNotNull" | "like" | "notLike" | "ilike" | "notiLike" | "regex" | "notRegex" | "iregex" | "notiRegex" | "in" | "notIn" | "isEmpty" | "isNotEmpty" | "isTrue" | "isFalse";
|
|
2
|
+
type SortOrder = "asc" | "desc";
|
|
3
|
+
interface QueryInputCondition {
|
|
4
|
+
field: string;
|
|
5
|
+
operator: QueryOperator;
|
|
6
|
+
value?: string | number;
|
|
7
|
+
values?: Array<string | number>;
|
|
8
|
+
}
|
|
9
|
+
interface QueryInput {
|
|
10
|
+
and?: QueryInput[];
|
|
11
|
+
or?: QueryInput[];
|
|
12
|
+
condition?: QueryInputCondition;
|
|
13
|
+
}
|
|
14
|
+
interface SortInput {
|
|
15
|
+
field: string;
|
|
16
|
+
order: SortOrder;
|
|
17
|
+
}
|
|
18
|
+
interface FilterInput {
|
|
19
|
+
query?: QueryInput[];
|
|
20
|
+
sortby?: SortInput[];
|
|
21
|
+
}
|
|
22
|
+
interface PaginationInput {
|
|
23
|
+
page?: number;
|
|
24
|
+
per_page?: number;
|
|
25
|
+
}
|
|
26
|
+
interface PaginationData {
|
|
27
|
+
total: number;
|
|
28
|
+
current_page: number;
|
|
29
|
+
per_page: number;
|
|
30
|
+
last_page: number;
|
|
31
|
+
has_more_pages: boolean;
|
|
32
|
+
}
|
|
33
|
+
interface SearchValueInput {
|
|
34
|
+
field: string;
|
|
35
|
+
value?: string;
|
|
36
|
+
not_found_behavior?: "create" | "ignore" | "error";
|
|
37
|
+
search_in_all_field_variants?: boolean;
|
|
38
|
+
multiple_results_allowed?: boolean;
|
|
39
|
+
multiple_results_use_latest?: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface EntityMetadata {
|
|
42
|
+
pascalName: string;
|
|
43
|
+
scalarFieldNames: string[];
|
|
44
|
+
variantFieldNames?: string[];
|
|
45
|
+
relations: Record<string, {
|
|
46
|
+
type: "hasOne" | "hasMany";
|
|
47
|
+
entity: string;
|
|
48
|
+
pascalName: string;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
interface SchemaMetadata {
|
|
52
|
+
entities: Record<string, EntityMetadata>;
|
|
53
|
+
unknownFieldBehavior?: "error" | "ignore";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type EntityName<S> = string & keyof S;
|
|
57
|
+
type EntityFields<S, E extends EntityName<S>> = S[E] extends {
|
|
58
|
+
fields: infer F;
|
|
59
|
+
} ? F : never;
|
|
60
|
+
type EntityRelations<S, E extends EntityName<S>> = S[E] extends {
|
|
61
|
+
relations: infer R;
|
|
62
|
+
} ? R : never;
|
|
63
|
+
type UpsertInput<S, E extends EntityName<S>> = S[E] extends {
|
|
64
|
+
updateInput: infer U;
|
|
65
|
+
} ? U : Record<string, unknown>;
|
|
66
|
+
type OmitNever<T> = {
|
|
67
|
+
[K in keyof T as T[K] extends never ? never : K]: T[K];
|
|
68
|
+
};
|
|
69
|
+
type EntitySelect<S, E extends EntityName<S>, Depth extends number = 0> = Depth extends 5 ? Record<string, true> : OmitNever<{
|
|
70
|
+
[K in keyof EntityFields<S, E>]?: true;
|
|
71
|
+
}> & {
|
|
72
|
+
[K in keyof EntityRelations<S, E>]?: EntityRelations<S, E>[K] extends {
|
|
73
|
+
type: "hasOne";
|
|
74
|
+
entity: infer Target;
|
|
75
|
+
} ? Target extends EntityName<S> ? true | {
|
|
76
|
+
select?: EntitySelect<S, Target, Increment[Depth]>;
|
|
77
|
+
filter?: FilterInput;
|
|
78
|
+
} : true : EntityRelations<S, E>[K] extends {
|
|
79
|
+
type: "hasMany";
|
|
80
|
+
entity: infer Target;
|
|
81
|
+
} ? Target extends EntityName<S> ? true | {
|
|
82
|
+
select?: EntitySelect<S, Target, Increment[Depth]>;
|
|
83
|
+
filter?: FilterInput;
|
|
84
|
+
pagination?: PaginationInput;
|
|
85
|
+
} : true : true;
|
|
86
|
+
};
|
|
87
|
+
type Increment = [1, 2, 3, 4, 5, 5];
|
|
88
|
+
type EntityResult<S, E extends EntityName<S>, Select extends EntitySelect<S, E> | undefined = undefined, Depth extends number = 0> = Depth extends 5 ? Record<string, unknown> : Select extends undefined ? EntityFields<S, E> : Select extends EntitySelect<S, E> ? OmitNever<ScalarResult<S, E, Select> & RelationResult<S, E, Select, Depth>> : EntityFields<S, E>;
|
|
89
|
+
type ScalarResult<S, E extends EntityName<S>, Select extends EntitySelect<S, E>> = {
|
|
90
|
+
[K in keyof Select & keyof EntityFields<S, E>]: Select[K] extends true ? EntityFields<S, E>[K] : never;
|
|
91
|
+
};
|
|
92
|
+
type RelationResult<S, E extends EntityName<S>, Select extends EntitySelect<S, E>, Depth extends number = 0> = {
|
|
93
|
+
[K in keyof Select & keyof EntityRelations<S, E>]: EntityRelations<S, E>[K] extends {
|
|
94
|
+
type: "hasOne";
|
|
95
|
+
entity: infer Target;
|
|
96
|
+
} ? Target extends EntityName<S> ? Select[K] extends {
|
|
97
|
+
select: infer SubSelect;
|
|
98
|
+
} ? SubSelect extends EntitySelect<S, Target> ? {
|
|
99
|
+
data: EntityResult<S, Target, SubSelect, Increment[Depth]>;
|
|
100
|
+
} | null : {
|
|
101
|
+
data: EntityFields<S, Target>;
|
|
102
|
+
} | null : Select[K] extends true ? {
|
|
103
|
+
data: EntityFields<S, Target>;
|
|
104
|
+
} | null : never : never : EntityRelations<S, E>[K] extends {
|
|
105
|
+
type: "hasMany";
|
|
106
|
+
entity: infer Target;
|
|
107
|
+
} ? Target extends EntityName<S> ? Select[K] extends {
|
|
108
|
+
select: infer SubSelect;
|
|
109
|
+
} ? SubSelect extends EntitySelect<S, Target> ? {
|
|
110
|
+
data: Array<EntityResult<S, Target, SubSelect, Increment[Depth]>>;
|
|
111
|
+
pagination: PaginationData;
|
|
112
|
+
} : {
|
|
113
|
+
data: Array<EntityFields<S, Target>>;
|
|
114
|
+
pagination: PaginationData;
|
|
115
|
+
} : Select[K] extends true ? {
|
|
116
|
+
data: Array<EntityFields<S, Target>>;
|
|
117
|
+
pagination: PaginationData;
|
|
118
|
+
} : never : never : never;
|
|
119
|
+
};
|
|
120
|
+
type StrictSelect<Sel, Valid> = Valid & {
|
|
121
|
+
[K in keyof Sel]: K extends keyof Valid ? Sel[K] : never;
|
|
122
|
+
};
|
|
123
|
+
type ListOptions<S, E extends EntityName<S>, Sel extends EntitySelect<S, E> | undefined = undefined> = {
|
|
124
|
+
select?: Sel extends undefined ? EntitySelect<S, E> : StrictSelect<Sel, EntitySelect<S, E>>;
|
|
125
|
+
filter?: FilterInput;
|
|
126
|
+
pagination?: PaginationInput;
|
|
127
|
+
};
|
|
128
|
+
type ByIdOptions<S, E extends EntityName<S>, Sel extends EntitySelect<S, E> | undefined = undefined> = {
|
|
129
|
+
select?: Sel extends undefined ? EntitySelect<S, E> : StrictSelect<Sel, EntitySelect<S, E>>;
|
|
130
|
+
};
|
|
131
|
+
interface ListResult<T> {
|
|
132
|
+
data: T[];
|
|
133
|
+
pagination: PaginationData;
|
|
134
|
+
}
|
|
135
|
+
interface RequestOptions {
|
|
136
|
+
headers?: Record<string, string>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
declare class PyloError extends Error {
|
|
140
|
+
graphqlErrors: unknown;
|
|
141
|
+
constructor(message: string, graphqlErrors?: unknown);
|
|
142
|
+
}
|
|
143
|
+
type AuthProvider = () => Promise<{
|
|
144
|
+
token?: string;
|
|
145
|
+
apiKey?: string;
|
|
146
|
+
}>;
|
|
147
|
+
interface ClientOptions {
|
|
148
|
+
endpoint?: string;
|
|
149
|
+
schemaMetadata: SchemaMetadata;
|
|
150
|
+
auth: AuthProvider;
|
|
151
|
+
headers?: Record<string, string>;
|
|
152
|
+
}
|
|
153
|
+
interface EntityClient<S, E extends EntityName<S>> {
|
|
154
|
+
list<Sel extends EntitySelect<S, E> | undefined = undefined>(options?: ListOptions<S, E, Sel> & RequestOptions): Promise<ListResult<EntityResult<S, E, Sel>>>;
|
|
155
|
+
byId<Sel extends EntitySelect<S, E> | undefined = undefined>(id: string, options?: ByIdOptions<S, E, Sel> & RequestOptions): Promise<EntityResult<S, E, Sel> | null>;
|
|
156
|
+
upsert(input: UpsertInput<S, E>, options?: RequestOptions): Promise<{
|
|
157
|
+
id: string;
|
|
158
|
+
}>;
|
|
159
|
+
delete(ids: string[], options?: RequestOptions): Promise<{
|
|
160
|
+
success: boolean;
|
|
161
|
+
}>;
|
|
162
|
+
}
|
|
163
|
+
type PyloClient<S> = {
|
|
164
|
+
[E in EntityName<S>]: EntityClient<S, E>;
|
|
165
|
+
};
|
|
3
166
|
|
|
4
167
|
interface NodeClientOptions {
|
|
5
168
|
apiKey: string;
|
|
@@ -9,4 +172,4 @@ interface NodeClientOptions {
|
|
|
9
172
|
}
|
|
10
173
|
declare function createPyloNode<S>(options: NodeClientOptions): PyloClient<S>;
|
|
11
174
|
|
|
12
|
-
export { createPyloNode };
|
|
175
|
+
export { type AuthProvider, type ByIdOptions, type ClientOptions, type EntityClient, type EntityFields, type EntityMetadata, type EntityName, type EntityRelations, type EntityResult, type EntitySelect, type FilterInput, type ListOptions, type ListResult, type PaginationData, type PaginationInput, type PyloClient, PyloError, type QueryInput, type QueryInputCondition, type QueryOperator, type RequestOptions, type SchemaMetadata, type SearchValueInput, type SortInput, type SortOrder, type StrictSelect, type UpsertInput, createPyloNode };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,372 @@
|
|
|
1
|
+
// ../auth/dist/index.js
|
|
2
|
+
var DEFAULT_GRAPHQL_ENDPOINT = "https://api.pyloapp.com/graphql";
|
|
3
|
+
function extractErrorMessage(errors) {
|
|
4
|
+
var _a, _b, _c, _d;
|
|
5
|
+
if (!errors) return null;
|
|
6
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
|
7
|
+
return (_b = (_a = errors[0]) == null ? void 0 : _a.message) != null ? _b : null;
|
|
8
|
+
}
|
|
9
|
+
if (typeof errors === "object" && "generalError" in errors) {
|
|
10
|
+
return (_d = (_c = errors.generalError) == null ? void 0 : _c.message) != null ? _d : null;
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
function hasErrors(response) {
|
|
15
|
+
return extractErrorMessage(response.errors) !== null;
|
|
16
|
+
}
|
|
17
|
+
function mergeHeaders(global, perOp) {
|
|
18
|
+
if (!global && !perOp) return void 0;
|
|
19
|
+
return { ...global, ...perOp };
|
|
20
|
+
}
|
|
21
|
+
var PROTECTED_HEADERS = /* @__PURE__ */ new Set(["authorization", "pylo-api-key", "content-type"]);
|
|
22
|
+
function stripProtectedHeaders(custom) {
|
|
23
|
+
const result = {};
|
|
24
|
+
for (const [key, value] of Object.entries(custom)) {
|
|
25
|
+
if (!PROTECTED_HEADERS.has(key.toLowerCase())) {
|
|
26
|
+
result[key] = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
async function graphqlRequest(endpoint, query, variables, options) {
|
|
32
|
+
const headers = {
|
|
33
|
+
...(options == null ? void 0 : options.headers) ? stripProtectedHeaders(options.headers) : {},
|
|
34
|
+
"Content-Type": "application/json"
|
|
35
|
+
};
|
|
36
|
+
if (options == null ? void 0 : options.token) {
|
|
37
|
+
headers["Authorization"] = `Bearer ${options.token}`;
|
|
38
|
+
}
|
|
39
|
+
if (options == null ? void 0 : options.apiKey) {
|
|
40
|
+
headers["pylo-api-key"] = options.apiKey;
|
|
41
|
+
}
|
|
42
|
+
const response = await fetch(endpoint, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers,
|
|
45
|
+
body: JSON.stringify({ query, variables })
|
|
46
|
+
});
|
|
47
|
+
return response.json();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ../core/dist/index.js
|
|
51
|
+
var PAGINATION_FIELDS = `pagination {
|
|
52
|
+
total
|
|
53
|
+
current_page
|
|
54
|
+
per_page
|
|
55
|
+
last_page
|
|
56
|
+
has_more_pages
|
|
57
|
+
}`;
|
|
58
|
+
function buildSelectionSet(select, entityMeta, schemaMetadata, variables, variableTypes, prefix) {
|
|
59
|
+
var _a, _b;
|
|
60
|
+
const fields = [];
|
|
61
|
+
const variantFieldNames = (_a = entityMeta.variantFieldNames) != null ? _a : [];
|
|
62
|
+
if (!select) {
|
|
63
|
+
fields.push(...entityMeta.scalarFieldNames);
|
|
64
|
+
for (const vf of variantFieldNames) {
|
|
65
|
+
fields.push(`${vf} { data { value variant } }`);
|
|
66
|
+
}
|
|
67
|
+
return fields.join("\n ");
|
|
68
|
+
}
|
|
69
|
+
for (const [key, value] of Object.entries(select)) {
|
|
70
|
+
if (entityMeta.scalarFieldNames.includes(key)) {
|
|
71
|
+
if (value === true) {
|
|
72
|
+
fields.push(key);
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (variantFieldNames.includes(key)) {
|
|
77
|
+
if (value === true) {
|
|
78
|
+
fields.push(`${key} { data { value variant } }`);
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const relation = entityMeta.relations[key];
|
|
83
|
+
if (!relation) {
|
|
84
|
+
if (schemaMetadata.unknownFieldBehavior === "ignore") continue;
|
|
85
|
+
const validFields = [...entityMeta.scalarFieldNames, ...variantFieldNames].join(", ");
|
|
86
|
+
const validRelations = Object.keys(entityMeta.relations).join(", ");
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Unknown field "${key}" on entity "${entityMeta.pascalName}". Valid fields: ${validFields}. Valid relations: ${validRelations}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const targetMeta = schemaMetadata.entities[relation.entity];
|
|
92
|
+
if (!targetMeta) continue;
|
|
93
|
+
const isHasMany = relation.type === "hasMany";
|
|
94
|
+
if (value === true) {
|
|
95
|
+
const targetFieldParts = [...targetMeta.scalarFieldNames];
|
|
96
|
+
for (const vf of (_b = targetMeta.variantFieldNames) != null ? _b : []) {
|
|
97
|
+
targetFieldParts.push(`${vf} { data { value variant } }`);
|
|
98
|
+
}
|
|
99
|
+
const targetFields = targetFieldParts.join(" ");
|
|
100
|
+
if (isHasMany) {
|
|
101
|
+
fields.push(`${key} { data { ${targetFields} } ${PAGINATION_FIELDS} }`);
|
|
102
|
+
} else {
|
|
103
|
+
fields.push(`${key} { data { ${targetFields} } }`);
|
|
104
|
+
}
|
|
105
|
+
} else if (typeof value === "object" && value !== null) {
|
|
106
|
+
const argParts = [];
|
|
107
|
+
if (value.filter !== void 0) {
|
|
108
|
+
const varName = `${prefix}${key}_filter`;
|
|
109
|
+
variables[varName] = value.filter;
|
|
110
|
+
variableTypes.set(varName, "FilterInput");
|
|
111
|
+
argParts.push(`filter: $${varName}`);
|
|
112
|
+
}
|
|
113
|
+
if (isHasMany && value.pagination !== void 0) {
|
|
114
|
+
const varName = `${prefix}${key}_pagination`;
|
|
115
|
+
variables[varName] = value.pagination;
|
|
116
|
+
variableTypes.set(varName, "PaginationInput");
|
|
117
|
+
argParts.push(`pagination: $${varName}`);
|
|
118
|
+
}
|
|
119
|
+
const args = argParts.length > 0 ? `(${argParts.join(", ")})` : "";
|
|
120
|
+
const nestedSelection = buildSelectionSet(
|
|
121
|
+
value.select,
|
|
122
|
+
targetMeta,
|
|
123
|
+
schemaMetadata,
|
|
124
|
+
variables,
|
|
125
|
+
variableTypes,
|
|
126
|
+
`${prefix}${key}_`
|
|
127
|
+
);
|
|
128
|
+
if (isHasMany) {
|
|
129
|
+
fields.push(
|
|
130
|
+
`${key}${args} { data { ${nestedSelection} } ${PAGINATION_FIELDS} }`
|
|
131
|
+
);
|
|
132
|
+
} else {
|
|
133
|
+
fields.push(`${key}${args} { data { ${nestedSelection} } }`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return fields.join("\n ");
|
|
138
|
+
}
|
|
139
|
+
function buildListQuery(entityKey, options, schemaMetadata) {
|
|
140
|
+
const entityMeta = schemaMetadata.entities[entityKey];
|
|
141
|
+
if (!entityMeta) {
|
|
142
|
+
throw new Error(`Unknown entity: ${entityKey}`);
|
|
143
|
+
}
|
|
144
|
+
const variables = {};
|
|
145
|
+
const variableTypes = /* @__PURE__ */ new Map();
|
|
146
|
+
if ((options == null ? void 0 : options.filter) !== void 0) {
|
|
147
|
+
variables["filter"] = options.filter;
|
|
148
|
+
variableTypes.set("filter", "FilterInput");
|
|
149
|
+
}
|
|
150
|
+
if ((options == null ? void 0 : options.pagination) !== void 0) {
|
|
151
|
+
variables["pagination"] = options.pagination;
|
|
152
|
+
variableTypes.set("pagination", "PaginationInput");
|
|
153
|
+
}
|
|
154
|
+
const selectionSet = buildSelectionSet(
|
|
155
|
+
options == null ? void 0 : options.select,
|
|
156
|
+
entityMeta,
|
|
157
|
+
schemaMetadata,
|
|
158
|
+
variables,
|
|
159
|
+
variableTypes,
|
|
160
|
+
"r_"
|
|
161
|
+
);
|
|
162
|
+
const varDecls = Array.from(variableTypes.entries()).map(([name, type]) => `$${name}: ${type}`).join(", ");
|
|
163
|
+
const varSection = varDecls ? `(${varDecls})` : "";
|
|
164
|
+
const argParts = [];
|
|
165
|
+
if (variableTypes.has("filter")) {
|
|
166
|
+
argParts.push("filter: $filter");
|
|
167
|
+
}
|
|
168
|
+
if (variableTypes.has("pagination")) {
|
|
169
|
+
argParts.push("pagination: $pagination");
|
|
170
|
+
}
|
|
171
|
+
const argSection = argParts.length > 0 ? `(${argParts.join(", ")})` : "";
|
|
172
|
+
const queryName = `${entityKey}List`;
|
|
173
|
+
const query = `query ${capitalize(entityKey)}List${varSection} {
|
|
174
|
+
${queryName}${argSection} {
|
|
175
|
+
data {
|
|
176
|
+
${selectionSet}
|
|
177
|
+
}
|
|
178
|
+
${PAGINATION_FIELDS}
|
|
179
|
+
}
|
|
180
|
+
}`;
|
|
181
|
+
return { query, variables };
|
|
182
|
+
}
|
|
183
|
+
function buildByIdQuery(entityKey, id, options, schemaMetadata) {
|
|
184
|
+
const entityMeta = schemaMetadata.entities[entityKey];
|
|
185
|
+
if (!entityMeta) {
|
|
186
|
+
throw new Error(`Unknown entity: ${entityKey}`);
|
|
187
|
+
}
|
|
188
|
+
const variables = { id };
|
|
189
|
+
const variableTypes = /* @__PURE__ */ new Map([["id", "ID!"]]);
|
|
190
|
+
const selectionSet = buildSelectionSet(
|
|
191
|
+
options == null ? void 0 : options.select,
|
|
192
|
+
entityMeta,
|
|
193
|
+
schemaMetadata,
|
|
194
|
+
variables,
|
|
195
|
+
variableTypes,
|
|
196
|
+
"r_"
|
|
197
|
+
);
|
|
198
|
+
const varDecls = Array.from(variableTypes.entries()).map(([name, type]) => `$${name}: ${type}`).join(", ");
|
|
199
|
+
const query = `query ${capitalize(entityKey)}ById(${varDecls}) {
|
|
200
|
+
${entityKey}ById(id: $id) {
|
|
201
|
+
data {
|
|
202
|
+
${selectionSet}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}`;
|
|
206
|
+
return { query, variables };
|
|
207
|
+
}
|
|
208
|
+
function capitalize(str) {
|
|
209
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
210
|
+
}
|
|
211
|
+
function buildUpsertMutation(_entityKey, pascalName, input) {
|
|
212
|
+
const mutation = `mutation Update${pascalName}($input: Update${pascalName}Input!) {
|
|
213
|
+
update${pascalName}(input: $input) {
|
|
214
|
+
data {
|
|
215
|
+
id
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}`;
|
|
219
|
+
return {
|
|
220
|
+
query: mutation,
|
|
221
|
+
variables: { input }
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function buildDeleteMutation(_entityKey, pascalName, ids) {
|
|
225
|
+
const mutation = `mutation Delete${pascalName}($ids: [ID!]!) {
|
|
226
|
+
delete${pascalName}(ids: $ids) {
|
|
227
|
+
data {
|
|
228
|
+
success
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}`;
|
|
232
|
+
return {
|
|
233
|
+
query: mutation,
|
|
234
|
+
variables: { ids }
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
var PyloError = class extends Error {
|
|
238
|
+
constructor(message, graphqlErrors) {
|
|
239
|
+
super(message);
|
|
240
|
+
this.name = "PyloError";
|
|
241
|
+
this.graphqlErrors = graphqlErrors;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
function getEndpoint(endpoint) {
|
|
245
|
+
var _a;
|
|
246
|
+
if (endpoint) return endpoint;
|
|
247
|
+
return (_a = process.env["PYLO_GRAPHQL_ENDPOINT"]) != null ? _a : DEFAULT_GRAPHQL_ENDPOINT;
|
|
248
|
+
}
|
|
249
|
+
async function executeGraphQL(endpoint, query, variables, auth, headers) {
|
|
250
|
+
var _a;
|
|
251
|
+
const credentials = await auth();
|
|
252
|
+
const response = await graphqlRequest(endpoint, query, variables, {
|
|
253
|
+
...credentials,
|
|
254
|
+
...headers !== void 0 ? { headers } : {}
|
|
255
|
+
});
|
|
256
|
+
if (hasErrors(response)) {
|
|
257
|
+
const message = (_a = extractErrorMessage(response.errors)) != null ? _a : "GraphQL request failed";
|
|
258
|
+
throw new PyloError(message, response.errors);
|
|
259
|
+
}
|
|
260
|
+
if (!response.data) {
|
|
261
|
+
throw new PyloError("No data returned from GraphQL request");
|
|
262
|
+
}
|
|
263
|
+
return response.data;
|
|
264
|
+
}
|
|
265
|
+
function createEntityClient(entityKey, endpoint, metadata, auth, globalHeaders) {
|
|
266
|
+
return {
|
|
267
|
+
async list(options) {
|
|
268
|
+
const { query, variables } = buildListQuery(
|
|
269
|
+
entityKey,
|
|
270
|
+
options,
|
|
271
|
+
metadata
|
|
272
|
+
);
|
|
273
|
+
const data = await executeGraphQL(
|
|
274
|
+
endpoint,
|
|
275
|
+
query,
|
|
276
|
+
variables,
|
|
277
|
+
auth,
|
|
278
|
+
mergeHeaders(globalHeaders, options == null ? void 0 : options.headers)
|
|
279
|
+
);
|
|
280
|
+
const listKey = `${entityKey}List`;
|
|
281
|
+
const result = data[listKey];
|
|
282
|
+
if (!result) {
|
|
283
|
+
throw new PyloError(`Unexpected response shape \u2014 missing ${listKey}`);
|
|
284
|
+
}
|
|
285
|
+
return { data: result.data, pagination: result.pagination };
|
|
286
|
+
},
|
|
287
|
+
async byId(id, options) {
|
|
288
|
+
const { query, variables } = buildByIdQuery(
|
|
289
|
+
entityKey,
|
|
290
|
+
id,
|
|
291
|
+
options,
|
|
292
|
+
metadata
|
|
293
|
+
);
|
|
294
|
+
const data = await executeGraphQL(
|
|
295
|
+
endpoint,
|
|
296
|
+
query,
|
|
297
|
+
variables,
|
|
298
|
+
auth,
|
|
299
|
+
mergeHeaders(globalHeaders, options == null ? void 0 : options.headers)
|
|
300
|
+
);
|
|
301
|
+
const byIdKey = `${entityKey}ById`;
|
|
302
|
+
const result = data[byIdKey];
|
|
303
|
+
if (!result) return null;
|
|
304
|
+
return result.data;
|
|
305
|
+
},
|
|
306
|
+
async upsert(input, options) {
|
|
307
|
+
const entityMeta = metadata.entities[entityKey];
|
|
308
|
+
if (!entityMeta) {
|
|
309
|
+
throw new PyloError(`Unknown entity: ${entityKey}`);
|
|
310
|
+
}
|
|
311
|
+
const { query, variables } = buildUpsertMutation(
|
|
312
|
+
entityKey,
|
|
313
|
+
entityMeta.pascalName,
|
|
314
|
+
input
|
|
315
|
+
);
|
|
316
|
+
const data = await executeGraphQL(
|
|
317
|
+
endpoint,
|
|
318
|
+
query,
|
|
319
|
+
variables,
|
|
320
|
+
auth,
|
|
321
|
+
mergeHeaders(globalHeaders, options == null ? void 0 : options.headers)
|
|
322
|
+
);
|
|
323
|
+
const mutationKey = `update${entityMeta.pascalName}`;
|
|
324
|
+
const result = data[mutationKey];
|
|
325
|
+
if (!result) {
|
|
326
|
+
throw new PyloError(`Unexpected response shape \u2014 missing ${mutationKey}`);
|
|
327
|
+
}
|
|
328
|
+
return result.data;
|
|
329
|
+
},
|
|
330
|
+
async delete(ids, options) {
|
|
331
|
+
const entityMeta = metadata.entities[entityKey];
|
|
332
|
+
if (!entityMeta) {
|
|
333
|
+
throw new PyloError(`Unknown entity: ${entityKey}`);
|
|
334
|
+
}
|
|
335
|
+
const { query, variables } = buildDeleteMutation(
|
|
336
|
+
entityKey,
|
|
337
|
+
entityMeta.pascalName,
|
|
338
|
+
ids
|
|
339
|
+
);
|
|
340
|
+
const data = await executeGraphQL(
|
|
341
|
+
endpoint,
|
|
342
|
+
query,
|
|
343
|
+
variables,
|
|
344
|
+
auth,
|
|
345
|
+
mergeHeaders(globalHeaders, options == null ? void 0 : options.headers)
|
|
346
|
+
);
|
|
347
|
+
const mutationKey = `delete${entityMeta.pascalName}`;
|
|
348
|
+
const result = data[mutationKey];
|
|
349
|
+
if (!result) {
|
|
350
|
+
throw new PyloError(`Unexpected response shape \u2014 missing ${mutationKey}`);
|
|
351
|
+
}
|
|
352
|
+
return result.data;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function createPyloClient(options) {
|
|
357
|
+
const endpoint = getEndpoint(options.endpoint);
|
|
358
|
+
const metadata = options.schemaMetadata;
|
|
359
|
+
const auth = options.auth;
|
|
360
|
+
const globalHeaders = options.headers;
|
|
361
|
+
return new Proxy({}, {
|
|
362
|
+
get(_target, prop) {
|
|
363
|
+
if (typeof prop !== "string") return void 0;
|
|
364
|
+
return createEntityClient(prop, endpoint, metadata, auth, globalHeaders);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
1
369
|
// src/index.ts
|
|
2
|
-
import { createPyloClient } from "@pylo/core";
|
|
3
|
-
import { PyloError } from "@pylo/core";
|
|
4
370
|
function createPyloNode(options) {
|
|
5
371
|
return createPyloClient({
|
|
6
372
|
...options.endpoint !== void 0 ? { endpoint: options.endpoint } : {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pylo/node",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Server-side Pylo SDK with API key authentication",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -22,12 +22,14 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"
|
|
25
|
+
"jiti": "^2.6.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.15.21",
|
|
29
29
|
"tsup": "^8.5.1",
|
|
30
|
-
"typescript": "^5.9.3"
|
|
30
|
+
"typescript": "^5.9.3",
|
|
31
|
+
"@pylo/core": "0.0.8",
|
|
32
|
+
"@pylo/auth": "0.0.4"
|
|
31
33
|
},
|
|
32
34
|
"license": "ISC",
|
|
33
35
|
"scripts": {
|