@pylo/node 0.0.12 → 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/dist/cli.js +47 -65
- package/dist/codegen.d.ts +1 -0
- package/dist/index.d.ts +73 -45
- package/dist/index.js +71 -115
- package/dist/schema.d.ts +1 -0
- package/dist/schema.js +406 -0
- package/dist/shared-types-DqRv2JHW.d.ts +67 -0
- package/package.json +11 -4
package/dist/cli.js
CHANGED
|
@@ -34,7 +34,6 @@ async function loadConfig(cwd) {
|
|
|
34
34
|
}
|
|
35
35
|
return {
|
|
36
36
|
endpoint: DEFAULT_ENDPOINT,
|
|
37
|
-
unknownFieldBehavior: "error",
|
|
38
37
|
...config
|
|
39
38
|
};
|
|
40
39
|
}
|
|
@@ -124,18 +123,15 @@ query PyloSchemaFetch($pagination: PaginationInput) {
|
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
125
|
`;
|
|
127
|
-
async function
|
|
126
|
+
async function fetchSchemaWith(request) {
|
|
128
127
|
var _a, _b;
|
|
129
128
|
const allEntities = [];
|
|
130
129
|
let page = 1;
|
|
131
130
|
let hasMore = true;
|
|
132
131
|
while (hasMore) {
|
|
133
|
-
const response = await
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
{ pagination: { page, per_page: 50 } },
|
|
137
|
-
config.apiKey
|
|
138
|
-
);
|
|
132
|
+
const response = await request(ENTITY_LIST_QUERY, {
|
|
133
|
+
pagination: { page, per_page: 50 }
|
|
134
|
+
});
|
|
139
135
|
if (response.errors) {
|
|
140
136
|
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";
|
|
141
137
|
throw new Error(`Failed to fetch schema: ${errMsg}`);
|
|
@@ -150,6 +146,11 @@ async function fetchSchema(config) {
|
|
|
150
146
|
}
|
|
151
147
|
return allEntities;
|
|
152
148
|
}
|
|
149
|
+
async function fetchSchema(config) {
|
|
150
|
+
return fetchSchemaWith(
|
|
151
|
+
(query, variables) => graphqlRequestWithApiKey(config.endpoint, query, variables, config.apiKey)
|
|
152
|
+
);
|
|
153
|
+
}
|
|
153
154
|
var DATA_TYPE_MAP = {
|
|
154
155
|
TEXT: "string",
|
|
155
156
|
LONGTEXT: "string",
|
|
@@ -263,6 +264,7 @@ function analyzeEntities(rawEntities) {
|
|
|
263
264
|
});
|
|
264
265
|
}
|
|
265
266
|
var VARIANT_TYPE = "{ variant: string; value: string }";
|
|
267
|
+
var REGISTERABLE_SOURCES = /* @__PURE__ */ new Set(["@pylo/node", "@pylo/nextjs"]);
|
|
266
268
|
function fieldTypeString(field) {
|
|
267
269
|
if (field.nullable) {
|
|
268
270
|
return `${field.tsType} | null`;
|
|
@@ -276,10 +278,21 @@ function indent(text, level) {
|
|
|
276
278
|
function getVariantFieldNames(entity) {
|
|
277
279
|
return entity.fields.filter((f) => f.variantFieldName !== null).map((f) => f.variantFieldName);
|
|
278
280
|
}
|
|
281
|
+
var NON_WRITABLE_FIELDS = /* @__PURE__ */ new Set(["integer_id", "created_at", "updated_at"]);
|
|
282
|
+
function generateReplaceVariablesField(entity) {
|
|
283
|
+
const fieldNames = entity.fields.map((f) => f.name).filter((name) => !NON_WRITABLE_FIELDS.has(name));
|
|
284
|
+
if (fieldNames.length === 0) return null;
|
|
285
|
+
const union = fieldNames.map((name) => `'${name}'`).join(" | ");
|
|
286
|
+
return [
|
|
287
|
+
"/**",
|
|
288
|
+
" * Field names whose template variables (e.g. `${replace_uuid.myNewEntity}`) the server",
|
|
289
|
+
" * should resolve to their concrete values during this upsert.",
|
|
290
|
+
" */",
|
|
291
|
+
`replace_variables?: Array<${union}>;`
|
|
292
|
+
].join("\n");
|
|
293
|
+
}
|
|
279
294
|
function generateEntityFieldsType(entity) {
|
|
280
|
-
const lines = entity.fields.map(
|
|
281
|
-
(f) => `${f.name}: ${fieldTypeString(f)};`
|
|
282
|
-
);
|
|
295
|
+
const lines = entity.fields.map((f) => `${f.name}: ${fieldTypeString(f)};`);
|
|
283
296
|
for (const variantName of getVariantFieldNames(entity)) {
|
|
284
297
|
lines.push(`${variantName}: ${VARIANT_TYPE}[] | null;`);
|
|
285
298
|
}
|
|
@@ -305,9 +318,7 @@ function generateCreateInputType(entity) {
|
|
|
305
318
|
for (const rel of entity.relations) {
|
|
306
319
|
for (const suffix of rel.suffixes) {
|
|
307
320
|
const valueType = rel.type === "hasMany" ? "Record<string, unknown>[]" : "Record<string, unknown>";
|
|
308
|
-
lines.push(
|
|
309
|
-
`${rel.fieldName}${suffix}?: ${valueType};`
|
|
310
|
-
);
|
|
321
|
+
lines.push(`${rel.fieldName}${suffix}?: ${valueType};`);
|
|
311
322
|
}
|
|
312
323
|
}
|
|
313
324
|
return lines.join("\n");
|
|
@@ -318,6 +329,10 @@ function generateUpdateInputType(entity) {
|
|
|
318
329
|
lines.push(
|
|
319
330
|
"__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 };"
|
|
320
331
|
);
|
|
332
|
+
const replaceVariables = generateReplaceVariablesField(entity);
|
|
333
|
+
if (replaceVariables) {
|
|
334
|
+
lines.push(replaceVariables);
|
|
335
|
+
}
|
|
321
336
|
for (const field of entity.fields) {
|
|
322
337
|
if (field.name === "id" || field.name === "integer_id") continue;
|
|
323
338
|
if (field.name === "created_at" || field.name === "updated_at") continue;
|
|
@@ -329,9 +344,7 @@ function generateUpdateInputType(entity) {
|
|
|
329
344
|
for (const rel of entity.relations) {
|
|
330
345
|
for (const suffix of rel.suffixes) {
|
|
331
346
|
const valueType = rel.type === "hasMany" ? "Record<string, unknown>[]" : "Record<string, unknown>";
|
|
332
|
-
lines.push(
|
|
333
|
-
`${rel.fieldName}${suffix}?: ${valueType};`
|
|
334
|
-
);
|
|
347
|
+
lines.push(`${rel.fieldName}${suffix}?: ${valueType};`);
|
|
335
348
|
}
|
|
336
349
|
}
|
|
337
350
|
return lines.join("\n");
|
|
@@ -362,8 +375,6 @@ function generateIndexFile(entities, importSource) {
|
|
|
362
375
|
" SortInput,",
|
|
363
376
|
" SortOrder,",
|
|
364
377
|
" SearchValueInput,",
|
|
365
|
-
" SchemaMetadata,",
|
|
366
|
-
" EntityMetadata,",
|
|
367
378
|
`} from '${importSource}';`,
|
|
368
379
|
""
|
|
369
380
|
);
|
|
@@ -400,6 +411,16 @@ function generateIndexFile(entities, importSource) {
|
|
|
400
411
|
lines.push(" };");
|
|
401
412
|
}
|
|
402
413
|
lines.push("}", "");
|
|
414
|
+
if (REGISTERABLE_SOURCES.has(importSource)) {
|
|
415
|
+
lines.push(
|
|
416
|
+
`declare module '${importSource}' {`,
|
|
417
|
+
" interface PyloRegister {",
|
|
418
|
+
" schema: PyloSchema;",
|
|
419
|
+
" }",
|
|
420
|
+
"}",
|
|
421
|
+
""
|
|
422
|
+
);
|
|
423
|
+
}
|
|
403
424
|
return lines.join("\n");
|
|
404
425
|
}
|
|
405
426
|
function generateEntitiesFile(entities, importSource) {
|
|
@@ -425,57 +446,19 @@ function generateEntitiesFile(entities, importSource) {
|
|
|
425
446
|
}
|
|
426
447
|
for (const rel of entity.relations) {
|
|
427
448
|
if (rel.type === "hasOne") {
|
|
428
|
-
lines.push(
|
|
449
|
+
lines.push(
|
|
450
|
+
` ${rel.fieldName}?: { data: ${rel.targetEntityPascalName} } | null;`
|
|
451
|
+
);
|
|
429
452
|
} else {
|
|
430
|
-
lines.push(
|
|
453
|
+
lines.push(
|
|
454
|
+
` ${rel.fieldName}?: { data: ${rel.targetEntityPascalName}[]; pagination: PaginationData };`
|
|
455
|
+
);
|
|
431
456
|
}
|
|
432
457
|
}
|
|
433
458
|
lines.push("}", "");
|
|
434
459
|
}
|
|
435
460
|
return lines.join("\n");
|
|
436
461
|
}
|
|
437
|
-
function generateSchemaMetadataFile(entities, unknownFieldBehavior, importSource) {
|
|
438
|
-
const lines = [];
|
|
439
|
-
lines.push(
|
|
440
|
-
"// Auto-generated by @pylo/core codegen \u2014 DO NOT EDIT",
|
|
441
|
-
"",
|
|
442
|
-
`import type { SchemaMetadata } from '${importSource}';`,
|
|
443
|
-
"",
|
|
444
|
-
"export const schemaMetadata: SchemaMetadata = {",
|
|
445
|
-
` unknownFieldBehavior: '${unknownFieldBehavior}',`,
|
|
446
|
-
" entities: {"
|
|
447
|
-
);
|
|
448
|
-
for (const entity of entities) {
|
|
449
|
-
lines.push(` ${entity.key}: {`);
|
|
450
|
-
lines.push(` pascalName: '${entity.pascalName}',`);
|
|
451
|
-
const scalarNames = entity.fields.map((f) => `'${f.name}'`).join(", ");
|
|
452
|
-
lines.push(` scalarFieldNames: [${scalarNames}],`);
|
|
453
|
-
const variantNames = getVariantFieldNames(entity);
|
|
454
|
-
if (variantNames.length > 0) {
|
|
455
|
-
const variantNamesStr = variantNames.map((n) => `'${n}'`).join(", ");
|
|
456
|
-
lines.push(` variantFieldNames: [${variantNamesStr}],`);
|
|
457
|
-
}
|
|
458
|
-
const enumFields = entity.fields.filter((f) => f.enum !== null);
|
|
459
|
-
if (enumFields.length > 0) {
|
|
460
|
-
lines.push(" enumFields: {");
|
|
461
|
-
for (const f of enumFields) {
|
|
462
|
-
const vals = f.enum.values.map((v) => `'${v}'`).join(", ");
|
|
463
|
-
lines.push(` ${f.name}: [${vals}],`);
|
|
464
|
-
}
|
|
465
|
-
lines.push(" },");
|
|
466
|
-
}
|
|
467
|
-
lines.push(" relations: {");
|
|
468
|
-
for (const rel of entity.relations) {
|
|
469
|
-
lines.push(
|
|
470
|
-
` ${rel.fieldName}: { type: '${rel.type}', entity: '${rel.targetEntityKey}', pascalName: '${rel.targetEntityPascalName}' },`
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
lines.push(" },");
|
|
474
|
-
lines.push(" },");
|
|
475
|
-
}
|
|
476
|
-
lines.push(" },", "};", "");
|
|
477
|
-
return lines.join("\n");
|
|
478
|
-
}
|
|
479
462
|
function writeGeneratedFiles(outputDir, files) {
|
|
480
463
|
mkdirSync(outputDir, { recursive: true });
|
|
481
464
|
for (const [filename, content] of Object.entries(files)) {
|
|
@@ -499,8 +482,7 @@ async function generate(options) {
|
|
|
499
482
|
console.log("Generating types...");
|
|
500
483
|
const files = {
|
|
501
484
|
"index.ts": generateIndexFile(entities, importSource),
|
|
502
|
-
"entities.ts": generateEntitiesFile(entities, importSource)
|
|
503
|
-
"schema-metadata.ts": generateSchemaMetadataFile(entities, config.unknownFieldBehavior, importSource)
|
|
485
|
+
"entities.ts": generateEntitiesFile(entities, importSource)
|
|
504
486
|
};
|
|
505
487
|
console.log(`Writing files to ${outputDir}...`);
|
|
506
488
|
writeGeneratedFiles(outputDir, files);
|
package/dist/codegen.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -98,22 +98,6 @@ interface PyloEventFieldValuesOptions {
|
|
|
98
98
|
startTime?: string;
|
|
99
99
|
limit?: number;
|
|
100
100
|
}
|
|
101
|
-
interface EntityMetadata {
|
|
102
|
-
pascalName: string;
|
|
103
|
-
scalarFieldNames: string[];
|
|
104
|
-
variantFieldNames?: string[];
|
|
105
|
-
jsonFieldNames?: string[];
|
|
106
|
-
enumFields?: Record<string, string[]>;
|
|
107
|
-
relations: Record<string, {
|
|
108
|
-
type: "hasOne" | "hasMany";
|
|
109
|
-
entity: string;
|
|
110
|
-
pascalName: string;
|
|
111
|
-
}>;
|
|
112
|
-
}
|
|
113
|
-
interface SchemaMetadata {
|
|
114
|
-
entities: Record<string, EntityMetadata>;
|
|
115
|
-
unknownFieldBehavior?: "error" | "ignore";
|
|
116
|
-
}
|
|
117
101
|
|
|
118
102
|
type EntityName<S> = string & keyof S;
|
|
119
103
|
type EntityFields<S, E extends EntityName<S>> = S[E] extends {
|
|
@@ -128,26 +112,26 @@ type UpsertInput<S, E extends EntityName<S>> = S[E] extends {
|
|
|
128
112
|
type OmitNever<T> = {
|
|
129
113
|
[K in keyof T as T[K] extends never ? never : K]: T[K];
|
|
130
114
|
};
|
|
131
|
-
type EntitySelect<S, E extends EntityName<S>, Depth extends number = 0> = Depth extends 5 ? Record<string,
|
|
115
|
+
type EntitySelect<S, E extends EntityName<S>, Depth extends number = 0> = Depth extends 5 ? Record<string, never> : OmitNever<{
|
|
132
116
|
[K in keyof EntityFields<S, E>]?: true;
|
|
133
117
|
}> & {
|
|
134
118
|
[K in keyof EntityRelations<S, E>]?: EntityRelations<S, E>[K] extends {
|
|
135
119
|
type: "hasOne";
|
|
136
120
|
entity: infer Target;
|
|
137
|
-
} ? Target extends EntityName<S> ?
|
|
138
|
-
select
|
|
121
|
+
} ? Target extends EntityName<S> ? {
|
|
122
|
+
select: EntitySelect<S, Target, Increment[Depth]>;
|
|
139
123
|
filter?: FilterInput;
|
|
140
|
-
} :
|
|
124
|
+
} : never : EntityRelations<S, E>[K] extends {
|
|
141
125
|
type: "hasMany";
|
|
142
126
|
entity: infer Target;
|
|
143
|
-
} ? Target extends EntityName<S> ?
|
|
144
|
-
select
|
|
127
|
+
} ? Target extends EntityName<S> ? {
|
|
128
|
+
select: EntitySelect<S, Target, Increment[Depth]>;
|
|
145
129
|
filter?: FilterInput;
|
|
146
130
|
pagination?: PaginationInput;
|
|
147
|
-
} :
|
|
131
|
+
} : never : never;
|
|
148
132
|
};
|
|
149
133
|
type Increment = [1, 2, 3, 4, 5, 5];
|
|
150
|
-
type EntityResult<S, E extends EntityName<S>, Select extends EntitySelect<S, E
|
|
134
|
+
type EntityResult<S, E extends EntityName<S>, Select extends EntitySelect<S, E>, Depth extends number = 0> = Depth extends 5 ? Record<string, unknown> : Select extends EntitySelect<S, E> ? OmitNever<ScalarResult<S, E, Select> & RelationResult<S, E, Select, Depth>> : EntityFields<S, E>;
|
|
151
135
|
type ScalarResult<S, E extends EntityName<S>, Select extends EntitySelect<S, E>> = {
|
|
152
136
|
[K in keyof Select & keyof EntityFields<S, E>]: Select[K] extends true ? EntityFields<S, E>[K] : never;
|
|
153
137
|
};
|
|
@@ -159,36 +143,29 @@ type RelationResult<S, E extends EntityName<S>, Select extends EntitySelect<S, E
|
|
|
159
143
|
select: infer SubSelect;
|
|
160
144
|
} ? SubSelect extends EntitySelect<S, Target> ? {
|
|
161
145
|
data: EntityResult<S, Target, SubSelect, Increment[Depth]>;
|
|
162
|
-
} | null : {
|
|
163
|
-
data: EntityFields<S, Target>;
|
|
164
|
-
} | null : Select[K] extends true ? {
|
|
165
|
-
data: EntityFields<S, Target>;
|
|
166
|
-
} | null : never : never : EntityRelations<S, E>[K] extends {
|
|
146
|
+
} | null : never : never : never : EntityRelations<S, E>[K] extends {
|
|
167
147
|
type: "hasMany";
|
|
168
148
|
entity: infer Target;
|
|
169
149
|
} ? Target extends EntityName<S> ? Select[K] extends {
|
|
170
150
|
select: infer SubSelect;
|
|
171
151
|
} ? SubSelect extends EntitySelect<S, Target> ? {
|
|
172
152
|
data: Array<EntityResult<S, Target, SubSelect, Increment[Depth]>>;
|
|
153
|
+
} & (Select[K] extends {
|
|
154
|
+
pagination: unknown;
|
|
155
|
+
} ? {
|
|
173
156
|
pagination: PaginationData;
|
|
174
|
-
} :
|
|
175
|
-
data: Array<EntityFields<S, Target>>;
|
|
176
|
-
pagination: PaginationData;
|
|
177
|
-
} : Select[K] extends true ? {
|
|
178
|
-
data: Array<EntityFields<S, Target>>;
|
|
179
|
-
pagination: PaginationData;
|
|
180
|
-
} : never : never : never;
|
|
157
|
+
} : unknown) : never : never : never : never;
|
|
181
158
|
};
|
|
182
159
|
type StrictSelect<Sel, Valid> = Valid & {
|
|
183
160
|
[K in keyof Sel]: K extends keyof Valid ? Sel[K] : never;
|
|
184
161
|
};
|
|
185
|
-
type ListOptions<S, E extends EntityName<S>, Sel extends EntitySelect<S, E
|
|
186
|
-
select
|
|
162
|
+
type ListOptions<S, E extends EntityName<S>, Sel extends EntitySelect<S, E>> = {
|
|
163
|
+
select: StrictSelect<Sel, EntitySelect<S, E>>;
|
|
187
164
|
filter?: FilterInput;
|
|
188
165
|
pagination?: PaginationInput;
|
|
189
166
|
};
|
|
190
|
-
type ByIdOptions<S, E extends EntityName<S>, Sel extends EntitySelect<S, E
|
|
191
|
-
select
|
|
167
|
+
type ByIdOptions<S, E extends EntityName<S>, Sel extends EntitySelect<S, E>> = {
|
|
168
|
+
select: StrictSelect<Sel, EntitySelect<S, E>>;
|
|
192
169
|
};
|
|
193
170
|
interface ListResult<T> {
|
|
194
171
|
data: T[];
|
|
@@ -211,13 +188,12 @@ type AuthProvider = () => Promise<{
|
|
|
211
188
|
}>;
|
|
212
189
|
interface ClientOptions {
|
|
213
190
|
endpoint?: string;
|
|
214
|
-
schemaMetadata: SchemaMetadata;
|
|
215
191
|
auth: AuthProvider;
|
|
216
192
|
headers?: Record<string, string>;
|
|
217
193
|
}
|
|
218
194
|
interface EntityClient<S, E extends EntityName<S>> {
|
|
219
|
-
list<Sel extends EntitySelect<S, E
|
|
220
|
-
byId<Sel extends EntitySelect<S, E
|
|
195
|
+
list<Sel extends EntitySelect<S, E>>(options: ListOptions<S, E, Sel> & RequestOptions): Promise<ListResult<EntityResult<S, E, Sel>>>;
|
|
196
|
+
byId<Sel extends EntitySelect<S, E>>(id: string, options: ByIdOptions<S, E, Sel> & RequestOptions): Promise<EntityResult<S, E, Sel> | null>;
|
|
221
197
|
upsert(input: UpsertInput<S, E>, options?: MutationRequestOptions): Promise<{
|
|
222
198
|
id: string;
|
|
223
199
|
}>;
|
|
@@ -241,9 +217,61 @@ type PyloClient<S> = {
|
|
|
241
217
|
interface NodeClientOptions {
|
|
242
218
|
apiKey: string;
|
|
243
219
|
endpoint?: string;
|
|
244
|
-
schemaMetadata: SchemaMetadata;
|
|
245
220
|
headers?: Record<string, string>;
|
|
246
221
|
}
|
|
247
222
|
declare function createPyloNode<S>(options: NodeClientOptions): PyloClient<S>;
|
|
223
|
+
/**
|
|
224
|
+
* Augmentable registry that lets a host pin the schema type for the injected
|
|
225
|
+
* `pylo` client. Generated code augments it, e.g.:
|
|
226
|
+
*
|
|
227
|
+
* declare module "@pylo/node" {
|
|
228
|
+
* interface PyloRegister { schema: PyloSchema }
|
|
229
|
+
* }
|
|
230
|
+
*
|
|
231
|
+
* With no augmentation the client falls back to an untyped (`any`) schema.
|
|
232
|
+
*/
|
|
233
|
+
interface PyloRegister {
|
|
234
|
+
}
|
|
235
|
+
type RegisteredSchema = PyloRegister extends {
|
|
236
|
+
schema: infer S;
|
|
237
|
+
} ? S : any;
|
|
238
|
+
/**
|
|
239
|
+
* Entity keys available on the registered schema — e.g. `"contact"`. Use as the
|
|
240
|
+
* type parameter for {@link PyloSelect} / {@link PyloResult}.
|
|
241
|
+
*/
|
|
242
|
+
type PyloEntity = EntityName<RegisteredSchema>;
|
|
243
|
+
/**
|
|
244
|
+
* A reusable, type-safe selection for one entity on the registered schema.
|
|
245
|
+
* Lets you define what to fetch once and reuse it across calls.
|
|
246
|
+
*
|
|
247
|
+
* Declare the selection with `satisfies` (not a plain `: PyloSelect<…>`
|
|
248
|
+
* annotation) so the exact set of selected fields is preserved — that's what
|
|
249
|
+
* keeps {@link PyloResult} precise:
|
|
250
|
+
*
|
|
251
|
+
* ```ts
|
|
252
|
+
* const contactSelect = {
|
|
253
|
+
* name: true,
|
|
254
|
+
* email: true,
|
|
255
|
+
* company: { select: { name: true } },
|
|
256
|
+
* } satisfies PyloSelect<"contact">;
|
|
257
|
+
*
|
|
258
|
+
* const { data } = await pylo.contact.list({ select: contactSelect });
|
|
259
|
+
* type Contact = PyloResult<"contact", typeof contactSelect>;
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
type PyloSelect<E extends PyloEntity> = EntitySelect<RegisteredSchema, E>;
|
|
263
|
+
/**
|
|
264
|
+
* The row type returned for a given entity and selection. Pair with
|
|
265
|
+
* `typeof <yourSelect>` (see {@link PyloSelect}) for an exact result type.
|
|
266
|
+
*/
|
|
267
|
+
type PyloResult<E extends PyloEntity, Sel extends PyloSelect<E>> = EntityResult<RegisteredSchema, E, Sel>;
|
|
268
|
+
/**
|
|
269
|
+
* Zero-config client for environments that inject a ready-made client onto
|
|
270
|
+
* `globalThis.__PYLO_FLOW_CLIENT__` — e.g. the Pylo flow worker, which builds
|
|
271
|
+
* the client from the flow's API key and the customer's schema before running
|
|
272
|
+
* an action. Property access is forwarded to the current global client at
|
|
273
|
+
* access time, so a worker reused across customers always sees the live one.
|
|
274
|
+
*/
|
|
275
|
+
declare const pylo: PyloClient<RegisteredSchema>;
|
|
248
276
|
|
|
249
|
-
export { type AggregateFunction, type AggregateInput, type AuthProvider, type ByIdOptions, type ClientOptions, type DimensionInput, type EntityClient, type EntityFields, type
|
|
277
|
+
export { type AggregateFunction, type AggregateInput, type AuthProvider, type ByIdOptions, type ClientOptions, type DimensionInput, type EntityClient, type EntityFields, type EntityName, type EntityRelations, type EntityResult, type EntitySelect, type EventListFilterInput, type EventListOptions, type EventsClient, type FilterInput, type IngestEvents, type ListOptions, type ListResult, type MutationRequestOptions, type PaginationData, type PaginationInput, type PyloClient, type PyloEntity, PyloError, type PyloEvent, type PyloEventFieldValue, type PyloEventFieldValuesOptions, type PyloEventInput, type PyloEventListResult, type PyloEventProperty, type PyloEventPropertyKeysOptions, type PyloRegister, type PyloResult, type PyloSelect, type QueryInput, type QueryInputCondition, type QueryOperator, type RegisteredSchema, type RequestOptions, type SearchValueInput, type SortInput, type SortOrder, type StrictSelect, type TimeBucketInput, type UpsertInput, createPyloNode, pylo };
|
package/dist/index.js
CHANGED
|
@@ -48,6 +48,7 @@ async function graphqlRequest(endpoint, query, variables, options) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// ../core/dist/index.js
|
|
51
|
+
var VARIANT_SUFFIX = "_variants";
|
|
51
52
|
var PAGINATION_FIELDS = `pagination {
|
|
52
53
|
total
|
|
53
54
|
current_page
|
|
@@ -55,92 +56,59 @@ var PAGINATION_FIELDS = `pagination {
|
|
|
55
56
|
last_page
|
|
56
57
|
has_more_pages
|
|
57
58
|
}`;
|
|
58
|
-
function buildSelectionSet(select,
|
|
59
|
-
var _a, _b;
|
|
59
|
+
function buildSelectionSet(select, variables, variableTypes, prefix) {
|
|
60
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
61
|
for (const [key, value] of Object.entries(select)) {
|
|
70
|
-
if (
|
|
71
|
-
if (
|
|
62
|
+
if (value === true) {
|
|
63
|
+
if (key.endsWith(VARIANT_SUFFIX)) {
|
|
64
|
+
fields.push(`${key} { data { value variant } }`);
|
|
65
|
+
} else {
|
|
72
66
|
fields.push(key);
|
|
73
67
|
}
|
|
74
68
|
continue;
|
|
75
69
|
}
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
if (typeof value !== "object" || value === null) continue;
|
|
71
|
+
const relation = value;
|
|
72
|
+
const argParts = [];
|
|
73
|
+
if (relation.filter !== void 0) {
|
|
74
|
+
const varName = `${prefix}${key}_filter`;
|
|
75
|
+
variables[varName] = relation.filter;
|
|
76
|
+
variableTypes.set(varName, "FilterInput");
|
|
77
|
+
argParts.push(`filter: $${varName}`);
|
|
81
78
|
}
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
`Unknown field "${key}" on entity "${entityMeta.pascalName}". Valid fields: ${validFields}. Valid relations: ${validRelations}`
|
|
89
|
-
);
|
|
79
|
+
const wantsPagination = relation.pagination !== void 0;
|
|
80
|
+
if (wantsPagination) {
|
|
81
|
+
const varName = `${prefix}${key}_pagination`;
|
|
82
|
+
variables[varName] = relation.pagination;
|
|
83
|
+
variableTypes.set(varName, "PaginationInput");
|
|
84
|
+
argParts.push(`pagination: $${varName}`);
|
|
90
85
|
}
|
|
91
|
-
const
|
|
92
|
-
if (!
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
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
|
-
}
|
|
86
|
+
const args = argParts.length > 0 ? `(${argParts.join(", ")})` : "";
|
|
87
|
+
if (!relation.select) {
|
|
88
|
+
throw new Error(`Relation "${key}" requires an explicit { select: {...} }.`);
|
|
89
|
+
}
|
|
90
|
+
const nestedSelection = buildSelectionSet(
|
|
91
|
+
relation.select,
|
|
92
|
+
variables,
|
|
93
|
+
variableTypes,
|
|
94
|
+
`${prefix}${key}_`
|
|
95
|
+
);
|
|
96
|
+
if (wantsPagination) {
|
|
97
|
+
fields.push(`${key}${args} { data { ${nestedSelection} } ${PAGINATION_FIELDS} }`);
|
|
98
|
+
} else {
|
|
99
|
+
fields.push(`${key}${args} { data { ${nestedSelection} } }`);
|
|
135
100
|
}
|
|
136
101
|
}
|
|
137
102
|
return fields.join("\n ");
|
|
138
103
|
}
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
throw new Error(`Unknown entity: ${entityKey}`);
|
|
104
|
+
function requireSelect(entityKey, operation, select) {
|
|
105
|
+
if (!select || typeof select !== "object" || Object.keys(select).length === 0) {
|
|
106
|
+
throw new Error(`${operation}("${entityKey}") requires an explicit 'select'.`);
|
|
143
107
|
}
|
|
108
|
+
return select;
|
|
109
|
+
}
|
|
110
|
+
function buildListQuery(entityKey, options) {
|
|
111
|
+
const select = requireSelect(entityKey, "list", options == null ? void 0 : options.select);
|
|
144
112
|
const variables = {};
|
|
145
113
|
const variableTypes = /* @__PURE__ */ new Map();
|
|
146
114
|
if ((options == null ? void 0 : options.filter) !== void 0) {
|
|
@@ -151,14 +119,7 @@ function buildListQuery(entityKey, options, schemaMetadata) {
|
|
|
151
119
|
variables["pagination"] = options.pagination;
|
|
152
120
|
variableTypes.set("pagination", "PaginationInput");
|
|
153
121
|
}
|
|
154
|
-
const selectionSet = buildSelectionSet(
|
|
155
|
-
options == null ? void 0 : options.select,
|
|
156
|
-
entityMeta,
|
|
157
|
-
schemaMetadata,
|
|
158
|
-
variables,
|
|
159
|
-
variableTypes,
|
|
160
|
-
"r_"
|
|
161
|
-
);
|
|
122
|
+
const selectionSet = buildSelectionSet(select, variables, variableTypes, "r_");
|
|
162
123
|
const varDecls = Array.from(variableTypes.entries()).map(([name, type]) => `$${name}: ${type}`).join(", ");
|
|
163
124
|
const varSection = varDecls ? `(${varDecls})` : "";
|
|
164
125
|
const argParts = [];
|
|
@@ -180,21 +141,11 @@ function buildListQuery(entityKey, options, schemaMetadata) {
|
|
|
180
141
|
}`;
|
|
181
142
|
return { query, variables };
|
|
182
143
|
}
|
|
183
|
-
function buildByIdQuery(entityKey, id, options
|
|
184
|
-
const
|
|
185
|
-
if (!entityMeta) {
|
|
186
|
-
throw new Error(`Unknown entity: ${entityKey}`);
|
|
187
|
-
}
|
|
144
|
+
function buildByIdQuery(entityKey, id, options) {
|
|
145
|
+
const select = requireSelect(entityKey, "byId", options == null ? void 0 : options.select);
|
|
188
146
|
const variables = { id };
|
|
189
147
|
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
|
-
);
|
|
148
|
+
const selectionSet = buildSelectionSet(select, variables, variableTypes, "r_");
|
|
198
149
|
const varDecls = Array.from(variableTypes.entries()).map(([name, type]) => `$${name}: ${type}`).join(", ");
|
|
199
150
|
const query = `query ${capitalize(entityKey)}ById(${varDecls}) {
|
|
200
151
|
${entityKey}ById(id: $id) {
|
|
@@ -369,13 +320,12 @@ async function executeGraphQL(endpoint, query, variables, auth, headers) {
|
|
|
369
320
|
}
|
|
370
321
|
return response.data;
|
|
371
322
|
}
|
|
372
|
-
function createEntityClient(entityKey, endpoint,
|
|
323
|
+
function createEntityClient(entityKey, endpoint, auth, globalHeaders) {
|
|
373
324
|
return {
|
|
374
325
|
async list(options) {
|
|
375
326
|
const { query, variables } = buildListQuery(
|
|
376
327
|
entityKey,
|
|
377
|
-
options
|
|
378
|
-
metadata
|
|
328
|
+
options
|
|
379
329
|
);
|
|
380
330
|
const data = await executeGraphQL(
|
|
381
331
|
endpoint,
|
|
@@ -395,8 +345,7 @@ function createEntityClient(entityKey, endpoint, metadata, auth, globalHeaders)
|
|
|
395
345
|
const { query, variables } = buildByIdQuery(
|
|
396
346
|
entityKey,
|
|
397
347
|
id,
|
|
398
|
-
options
|
|
399
|
-
metadata
|
|
348
|
+
options
|
|
400
349
|
);
|
|
401
350
|
const data = await executeGraphQL(
|
|
402
351
|
endpoint,
|
|
@@ -411,13 +360,10 @@ function createEntityClient(entityKey, endpoint, metadata, auth, globalHeaders)
|
|
|
411
360
|
return result.data;
|
|
412
361
|
},
|
|
413
362
|
async upsert(input, options) {
|
|
414
|
-
const
|
|
415
|
-
if (!entityMeta) {
|
|
416
|
-
throw new PyloError(`Unknown entity: ${entityKey}`);
|
|
417
|
-
}
|
|
363
|
+
const pascalName = capitalize(entityKey);
|
|
418
364
|
const { query, variables } = buildUpsertMutation(
|
|
419
365
|
entityKey,
|
|
420
|
-
|
|
366
|
+
pascalName,
|
|
421
367
|
input
|
|
422
368
|
);
|
|
423
369
|
const data = await executeGraphQL(
|
|
@@ -430,7 +376,7 @@ function createEntityClient(entityKey, endpoint, metadata, auth, globalHeaders)
|
|
|
430
376
|
flagsToHeaders(options != null ? options : {})
|
|
431
377
|
)
|
|
432
378
|
);
|
|
433
|
-
const mutationKey = `update${
|
|
379
|
+
const mutationKey = `update${pascalName}`;
|
|
434
380
|
const result = data[mutationKey];
|
|
435
381
|
if (!result) {
|
|
436
382
|
throw new PyloError(`Unexpected response shape \u2014 missing ${mutationKey}`);
|
|
@@ -438,13 +384,10 @@ function createEntityClient(entityKey, endpoint, metadata, auth, globalHeaders)
|
|
|
438
384
|
return result.data;
|
|
439
385
|
},
|
|
440
386
|
async delete(ids, options) {
|
|
441
|
-
const
|
|
442
|
-
if (!entityMeta) {
|
|
443
|
-
throw new PyloError(`Unknown entity: ${entityKey}`);
|
|
444
|
-
}
|
|
387
|
+
const pascalName = capitalize(entityKey);
|
|
445
388
|
const { query, variables } = buildDeleteMutation(
|
|
446
389
|
entityKey,
|
|
447
|
-
|
|
390
|
+
pascalName,
|
|
448
391
|
ids
|
|
449
392
|
);
|
|
450
393
|
const data = await executeGraphQL(
|
|
@@ -457,7 +400,7 @@ function createEntityClient(entityKey, endpoint, metadata, auth, globalHeaders)
|
|
|
457
400
|
flagsToHeaders(options != null ? options : {})
|
|
458
401
|
)
|
|
459
402
|
);
|
|
460
|
-
const mutationKey = `delete${
|
|
403
|
+
const mutationKey = `delete${pascalName}`;
|
|
461
404
|
const result = data[mutationKey];
|
|
462
405
|
if (!result) {
|
|
463
406
|
throw new PyloError(`Unexpected response shape \u2014 missing ${mutationKey}`);
|
|
@@ -531,7 +474,6 @@ function createEventsClient(endpoint, auth, globalHeaders) {
|
|
|
531
474
|
}
|
|
532
475
|
function createPyloClient(options) {
|
|
533
476
|
const endpoint = getEndpoint(options.endpoint);
|
|
534
|
-
const metadata = options.schemaMetadata;
|
|
535
477
|
const auth = options.auth;
|
|
536
478
|
const globalHeaders = options.headers;
|
|
537
479
|
const ingestEvents = createIngestEvents(endpoint, auth, globalHeaders);
|
|
@@ -541,7 +483,7 @@ function createPyloClient(options) {
|
|
|
541
483
|
if (typeof prop !== "string") return void 0;
|
|
542
484
|
if (prop === "ingestEvents") return ingestEvents;
|
|
543
485
|
if (prop === "events") return events;
|
|
544
|
-
return createEntityClient(prop, endpoint,
|
|
486
|
+
return createEntityClient(prop, endpoint, auth, globalHeaders);
|
|
545
487
|
}
|
|
546
488
|
});
|
|
547
489
|
}
|
|
@@ -550,12 +492,26 @@ function createPyloClient(options) {
|
|
|
550
492
|
function createPyloNode(options) {
|
|
551
493
|
return createPyloClient({
|
|
552
494
|
...options.endpoint !== void 0 ? { endpoint: options.endpoint } : {},
|
|
553
|
-
schemaMetadata: options.schemaMetadata,
|
|
554
495
|
auth: async () => ({ apiKey: options.apiKey }),
|
|
555
496
|
...options.headers !== void 0 ? { headers: options.headers } : {}
|
|
556
497
|
});
|
|
557
498
|
}
|
|
499
|
+
var pylo = new Proxy(
|
|
500
|
+
{},
|
|
501
|
+
{
|
|
502
|
+
get(_target, prop) {
|
|
503
|
+
const client = globalThis["__PYLO_FLOW_CLIENT__"];
|
|
504
|
+
if (!client) {
|
|
505
|
+
throw new Error(
|
|
506
|
+
"Pylo flow client unavailable (globalThis.__PYLO_FLOW_CLIENT__ is unset). `pylo` is only usable inside a Pylo flow action."
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
return client[prop];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
);
|
|
558
513
|
export {
|
|
559
514
|
PyloError,
|
|
560
|
-
createPyloNode
|
|
515
|
+
createPyloNode,
|
|
516
|
+
pylo
|
|
561
517
|
};
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AnalyzedEntity, AnalyzedField, AnalyzedRelation, ENTITY_LIST_QUERY, EntityListResponse, RawEntity, RawEntityField, RawEntityRelation, SchemaFetcher, analyzeEntities, fetchSchemaWith, generateEntitiesFile, generateIndexFile } from '@pylo/core/schema';
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// ../core/dist/schema.js
|
|
2
|
+
var ENTITY_LIST_QUERY = `
|
|
3
|
+
query PyloSchemaFetch($pagination: PaginationInput) {
|
|
4
|
+
entityList(pagination: $pagination) {
|
|
5
|
+
data {
|
|
6
|
+
name
|
|
7
|
+
shortcode
|
|
8
|
+
is_system_entity
|
|
9
|
+
entity_fields {
|
|
10
|
+
data {
|
|
11
|
+
name
|
|
12
|
+
data_type
|
|
13
|
+
validation_string
|
|
14
|
+
form_type
|
|
15
|
+
default_value
|
|
16
|
+
variant_entity_field {
|
|
17
|
+
data {
|
|
18
|
+
name
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
entity_field_enum_values {
|
|
22
|
+
data {
|
|
23
|
+
value
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
entity_relations {
|
|
29
|
+
data {
|
|
30
|
+
type
|
|
31
|
+
field_name
|
|
32
|
+
target_field_name
|
|
33
|
+
target_entity {
|
|
34
|
+
data {
|
|
35
|
+
name
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
entity_is_tightly_coupled
|
|
39
|
+
target_entity_is_tightly_coupled
|
|
40
|
+
allow_connect_create
|
|
41
|
+
allow_connect_existing
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
entity_related {
|
|
45
|
+
data {
|
|
46
|
+
type
|
|
47
|
+
field_name
|
|
48
|
+
target_field_name
|
|
49
|
+
entity {
|
|
50
|
+
data {
|
|
51
|
+
name
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
entity_is_tightly_coupled
|
|
55
|
+
target_entity_is_tightly_coupled
|
|
56
|
+
allow_connect_create
|
|
57
|
+
allow_connect_existing
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
pagination {
|
|
62
|
+
total
|
|
63
|
+
has_more_pages
|
|
64
|
+
current_page
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
async function fetchSchemaWith(request) {
|
|
70
|
+
var _a, _b;
|
|
71
|
+
const allEntities = [];
|
|
72
|
+
let page = 1;
|
|
73
|
+
let hasMore = true;
|
|
74
|
+
while (hasMore) {
|
|
75
|
+
const response = await request(ENTITY_LIST_QUERY, {
|
|
76
|
+
pagination: { page, per_page: 50 }
|
|
77
|
+
});
|
|
78
|
+
if (response.errors) {
|
|
79
|
+
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";
|
|
80
|
+
throw new Error(`Failed to fetch schema: ${errMsg}`);
|
|
81
|
+
}
|
|
82
|
+
if (!response.data) {
|
|
83
|
+
throw new Error("No data returned from schema fetch");
|
|
84
|
+
}
|
|
85
|
+
const { data, pagination } = response.data.entityList;
|
|
86
|
+
allEntities.push(...data);
|
|
87
|
+
hasMore = pagination.has_more_pages;
|
|
88
|
+
page++;
|
|
89
|
+
}
|
|
90
|
+
return allEntities;
|
|
91
|
+
}
|
|
92
|
+
var DATA_TYPE_MAP = {
|
|
93
|
+
TEXT: "string",
|
|
94
|
+
LONGTEXT: "string",
|
|
95
|
+
RICHTEXT: "string",
|
|
96
|
+
INT: "number",
|
|
97
|
+
FLOAT: "number",
|
|
98
|
+
JSON: "Record<string, unknown> | unknown[]",
|
|
99
|
+
DATE: "string",
|
|
100
|
+
DATETIME: "string",
|
|
101
|
+
TIME: "string",
|
|
102
|
+
BOOLEAN: "boolean"
|
|
103
|
+
};
|
|
104
|
+
function mapDataType(dataType) {
|
|
105
|
+
var _a;
|
|
106
|
+
return (_a = DATA_TYPE_MAP[dataType]) != null ? _a : "string";
|
|
107
|
+
}
|
|
108
|
+
function isNullable(field) {
|
|
109
|
+
return field.validation_string !== "required";
|
|
110
|
+
}
|
|
111
|
+
function classifyRelation(relationType) {
|
|
112
|
+
if (relationType === "ManyToOne" || relationType === "OneToOne") {
|
|
113
|
+
return "hasOne";
|
|
114
|
+
}
|
|
115
|
+
return "hasMany";
|
|
116
|
+
}
|
|
117
|
+
function classifyReverseRelation(relationType) {
|
|
118
|
+
if (relationType === "OneToMany" || relationType === "OneToOne") {
|
|
119
|
+
return "hasOne";
|
|
120
|
+
}
|
|
121
|
+
return "hasMany";
|
|
122
|
+
}
|
|
123
|
+
function getMutationSuffixes(relType) {
|
|
124
|
+
if (relType === "hasOne") {
|
|
125
|
+
return ["_set"];
|
|
126
|
+
}
|
|
127
|
+
return ["_set", "_connect", "_disconnect"];
|
|
128
|
+
}
|
|
129
|
+
function toEntityKey(pascalName) {
|
|
130
|
+
return pascalName.charAt(0).toLowerCase() + pascalName.slice(1);
|
|
131
|
+
}
|
|
132
|
+
function toPascalCase(snake) {
|
|
133
|
+
return snake.split("_").filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
134
|
+
}
|
|
135
|
+
function analyzeField(field, entityPascalName) {
|
|
136
|
+
var _a, _b, _c, _d;
|
|
137
|
+
const hasVariants = ((_b = (_a = field.variant_entity_field) == null ? void 0 : _a.data) == null ? void 0 : _b.name) != null;
|
|
138
|
+
const enumValues = (_d = (_c = field.entity_field_enum_values) == null ? void 0 : _c.data) != null ? _d : [];
|
|
139
|
+
if (enumValues.length > 0) {
|
|
140
|
+
const typeName = `${entityPascalName}${toPascalCase(field.name)}`;
|
|
141
|
+
const values = enumValues.map((v) => v.value);
|
|
142
|
+
return {
|
|
143
|
+
name: field.name,
|
|
144
|
+
tsType: typeName,
|
|
145
|
+
nullable: isNullable(field),
|
|
146
|
+
variantFieldName: hasVariants ? `${field.name}_variants` : null,
|
|
147
|
+
enum: { typeName, values }
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
name: field.name,
|
|
152
|
+
tsType: mapDataType(field.data_type),
|
|
153
|
+
nullable: isNullable(field),
|
|
154
|
+
variantFieldName: hasVariants ? `${field.name}_variants` : null,
|
|
155
|
+
enum: null
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function analyzeRelation(relation) {
|
|
159
|
+
var _a, _b;
|
|
160
|
+
const targetName = (_b = (_a = relation.target_entity) == null ? void 0 : _a.data) == null ? void 0 : _b.name;
|
|
161
|
+
if (!targetName || !relation.field_name) return null;
|
|
162
|
+
const relType = classifyRelation(relation.type);
|
|
163
|
+
return {
|
|
164
|
+
fieldName: relation.field_name,
|
|
165
|
+
type: relType,
|
|
166
|
+
targetEntityKey: toEntityKey(targetName),
|
|
167
|
+
targetEntityPascalName: targetName,
|
|
168
|
+
suffixes: getMutationSuffixes(relType)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function analyzeReverseRelation(relation) {
|
|
172
|
+
var _a, _b;
|
|
173
|
+
const sourceName = (_b = (_a = relation.entity) == null ? void 0 : _a.data) == null ? void 0 : _b.name;
|
|
174
|
+
if (!sourceName || !relation.target_field_name) return null;
|
|
175
|
+
const relType = classifyReverseRelation(relation.type);
|
|
176
|
+
return {
|
|
177
|
+
fieldName: relation.target_field_name,
|
|
178
|
+
type: relType,
|
|
179
|
+
targetEntityKey: toEntityKey(sourceName),
|
|
180
|
+
targetEntityPascalName: sourceName,
|
|
181
|
+
suffixes: getMutationSuffixes(relType)
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function analyzeEntities(rawEntities) {
|
|
185
|
+
return rawEntities.map((entity) => {
|
|
186
|
+
var _a, _b, _c, _d, _e, _f;
|
|
187
|
+
const fields = ((_b = (_a = entity.entity_fields) == null ? void 0 : _a.data) != null ? _b : []).map(
|
|
188
|
+
(f) => analyzeField(f, entity.name)
|
|
189
|
+
);
|
|
190
|
+
const forwardRelations = ((_d = (_c = entity.entity_relations) == null ? void 0 : _c.data) != null ? _d : []).map(analyzeRelation).filter((r) => r !== null);
|
|
191
|
+
const reverseRelations = ((_f = (_e = entity.entity_related) == null ? void 0 : _e.data) != null ? _f : []).map(analyzeReverseRelation).filter((r) => r !== null);
|
|
192
|
+
const seen = new Set(forwardRelations.map((r) => r.fieldName));
|
|
193
|
+
const deduped = reverseRelations.filter((r) => !seen.has(r.fieldName));
|
|
194
|
+
return {
|
|
195
|
+
key: toEntityKey(entity.name),
|
|
196
|
+
pascalName: entity.name,
|
|
197
|
+
shortcode: entity.shortcode,
|
|
198
|
+
isSystem: entity.is_system_entity,
|
|
199
|
+
fields,
|
|
200
|
+
relations: [...forwardRelations, ...deduped]
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
var VARIANT_TYPE = "{ variant: string; value: string }";
|
|
205
|
+
var REGISTERABLE_SOURCES = /* @__PURE__ */ new Set(["@pylo/node", "@pylo/nextjs"]);
|
|
206
|
+
function fieldTypeString(field) {
|
|
207
|
+
if (field.nullable) {
|
|
208
|
+
return `${field.tsType} | null`;
|
|
209
|
+
}
|
|
210
|
+
return field.tsType;
|
|
211
|
+
}
|
|
212
|
+
function indent(text, level) {
|
|
213
|
+
const spaces = " ".repeat(level);
|
|
214
|
+
return text.split("\n").map((line) => line.trim() ? spaces + line : line).join("\n");
|
|
215
|
+
}
|
|
216
|
+
function getVariantFieldNames(entity) {
|
|
217
|
+
return entity.fields.filter((f) => f.variantFieldName !== null).map((f) => f.variantFieldName);
|
|
218
|
+
}
|
|
219
|
+
var NON_WRITABLE_FIELDS = /* @__PURE__ */ new Set(["integer_id", "created_at", "updated_at"]);
|
|
220
|
+
function generateReplaceVariablesField(entity) {
|
|
221
|
+
const fieldNames = entity.fields.map((f) => f.name).filter((name) => !NON_WRITABLE_FIELDS.has(name));
|
|
222
|
+
if (fieldNames.length === 0) return null;
|
|
223
|
+
const union = fieldNames.map((name) => `'${name}'`).join(" | ");
|
|
224
|
+
return [
|
|
225
|
+
"/**",
|
|
226
|
+
" * Field names whose template variables (e.g. `${replace_uuid.myNewEntity}`) the server",
|
|
227
|
+
" * should resolve to their concrete values during this upsert.",
|
|
228
|
+
" */",
|
|
229
|
+
`replace_variables?: Array<${union}>;`
|
|
230
|
+
].join("\n");
|
|
231
|
+
}
|
|
232
|
+
function generateEntityFieldsType(entity) {
|
|
233
|
+
const lines = entity.fields.map((f) => `${f.name}: ${fieldTypeString(f)};`);
|
|
234
|
+
for (const variantName of getVariantFieldNames(entity)) {
|
|
235
|
+
lines.push(`${variantName}: ${VARIANT_TYPE}[] | null;`);
|
|
236
|
+
}
|
|
237
|
+
return lines.join("\n");
|
|
238
|
+
}
|
|
239
|
+
function generateEntityRelationsType(entity) {
|
|
240
|
+
if (entity.relations.length === 0) return "";
|
|
241
|
+
const lines = entity.relations.map(
|
|
242
|
+
(r) => `${r.fieldName}: { type: '${r.type}'; entity: '${r.targetEntityKey}' };`
|
|
243
|
+
);
|
|
244
|
+
return lines.join("\n");
|
|
245
|
+
}
|
|
246
|
+
function generateCreateInputType(entity) {
|
|
247
|
+
const lines = [];
|
|
248
|
+
for (const field of entity.fields) {
|
|
249
|
+
if (field.name === "id" || field.name === "integer_id") continue;
|
|
250
|
+
if (field.name === "created_at" || field.name === "updated_at") continue;
|
|
251
|
+
lines.push(`${field.name}?: ${fieldTypeString(field)};`);
|
|
252
|
+
}
|
|
253
|
+
for (const variantName of getVariantFieldNames(entity)) {
|
|
254
|
+
lines.push(`${variantName}?: ${VARIANT_TYPE}[];`);
|
|
255
|
+
}
|
|
256
|
+
for (const rel of entity.relations) {
|
|
257
|
+
for (const suffix of rel.suffixes) {
|
|
258
|
+
const valueType = rel.type === "hasMany" ? "Record<string, unknown>[]" : "Record<string, unknown>";
|
|
259
|
+
lines.push(`${rel.fieldName}${suffix}?: ${valueType};`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return lines.join("\n");
|
|
263
|
+
}
|
|
264
|
+
function generateUpdateInputType(entity) {
|
|
265
|
+
const lines = [];
|
|
266
|
+
lines.push("id?: string;");
|
|
267
|
+
lines.push(
|
|
268
|
+
"__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 };"
|
|
269
|
+
);
|
|
270
|
+
const replaceVariables = generateReplaceVariablesField(entity);
|
|
271
|
+
if (replaceVariables) {
|
|
272
|
+
lines.push(replaceVariables);
|
|
273
|
+
}
|
|
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
|
+
const valueType = rel.type === "hasMany" ? "Record<string, unknown>[]" : "Record<string, unknown>";
|
|
285
|
+
lines.push(`${rel.fieldName}${suffix}?: ${valueType};`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return lines.join("\n");
|
|
289
|
+
}
|
|
290
|
+
function collectEnumTypes(entities) {
|
|
291
|
+
const seen = /* @__PURE__ */ new Map();
|
|
292
|
+
for (const entity of entities) {
|
|
293
|
+
for (const field of entity.fields) {
|
|
294
|
+
if (field.enum && !seen.has(field.enum.typeName)) {
|
|
295
|
+
seen.set(field.enum.typeName, field.enum.values);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return Array.from(seen, ([typeName, values]) => ({ typeName, values }));
|
|
300
|
+
}
|
|
301
|
+
function generateIndexFile(entities, importSource) {
|
|
302
|
+
const lines = [];
|
|
303
|
+
lines.push(
|
|
304
|
+
"// Auto-generated by @pylo/core codegen \u2014 DO NOT EDIT",
|
|
305
|
+
"",
|
|
306
|
+
"export type {",
|
|
307
|
+
" FilterInput,",
|
|
308
|
+
" PaginationData,",
|
|
309
|
+
" PaginationInput,",
|
|
310
|
+
" QueryInput,",
|
|
311
|
+
" QueryInputCondition,",
|
|
312
|
+
" QueryOperator,",
|
|
313
|
+
" SortInput,",
|
|
314
|
+
" SortOrder,",
|
|
315
|
+
" SearchValueInput,",
|
|
316
|
+
`} from '${importSource}';`,
|
|
317
|
+
""
|
|
318
|
+
);
|
|
319
|
+
const enumTypes = collectEnumTypes(entities);
|
|
320
|
+
if (enumTypes.length > 0) {
|
|
321
|
+
for (const { typeName, values } of enumTypes) {
|
|
322
|
+
const union = values.map((v) => `'${v}'`).join(" | ");
|
|
323
|
+
lines.push(`export type ${typeName} = ${union};`);
|
|
324
|
+
}
|
|
325
|
+
lines.push("");
|
|
326
|
+
}
|
|
327
|
+
for (const entity of entities) {
|
|
328
|
+
lines.push(`export interface Create${entity.pascalName}Input {`);
|
|
329
|
+
lines.push(indent(generateCreateInputType(entity), 1));
|
|
330
|
+
lines.push("}", "");
|
|
331
|
+
lines.push(`export interface Update${entity.pascalName}Input {`);
|
|
332
|
+
lines.push(indent(generateUpdateInputType(entity), 1));
|
|
333
|
+
lines.push("}", "");
|
|
334
|
+
}
|
|
335
|
+
lines.push("export interface PyloSchema {");
|
|
336
|
+
for (const entity of entities) {
|
|
337
|
+
lines.push(` ${entity.key}: {`);
|
|
338
|
+
lines.push(" fields: {");
|
|
339
|
+
lines.push(indent(generateEntityFieldsType(entity), 3));
|
|
340
|
+
lines.push(" };");
|
|
341
|
+
const relType = generateEntityRelationsType(entity);
|
|
342
|
+
lines.push(" relations: {");
|
|
343
|
+
if (relType) {
|
|
344
|
+
lines.push(indent(relType, 3));
|
|
345
|
+
}
|
|
346
|
+
lines.push(" };");
|
|
347
|
+
lines.push(` createInput: Create${entity.pascalName}Input;`);
|
|
348
|
+
lines.push(` updateInput: Update${entity.pascalName}Input;`);
|
|
349
|
+
lines.push(" };");
|
|
350
|
+
}
|
|
351
|
+
lines.push("}", "");
|
|
352
|
+
if (REGISTERABLE_SOURCES.has(importSource)) {
|
|
353
|
+
lines.push(
|
|
354
|
+
`declare module '${importSource}' {`,
|
|
355
|
+
" interface PyloRegister {",
|
|
356
|
+
" schema: PyloSchema;",
|
|
357
|
+
" }",
|
|
358
|
+
"}",
|
|
359
|
+
""
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
return lines.join("\n");
|
|
363
|
+
}
|
|
364
|
+
function generateEntitiesFile(entities, importSource) {
|
|
365
|
+
const lines = [];
|
|
366
|
+
lines.push(
|
|
367
|
+
"// Auto-generated by @pylo/core codegen \u2014 DO NOT EDIT",
|
|
368
|
+
"",
|
|
369
|
+
`import type { PaginationData } from '${importSource}';`
|
|
370
|
+
);
|
|
371
|
+
const enumTypes = collectEnumTypes(entities);
|
|
372
|
+
if (enumTypes.length > 0) {
|
|
373
|
+
const names = enumTypes.map((e) => e.typeName).join(", ");
|
|
374
|
+
lines.push(`import type { ${names} } from './index.js';`);
|
|
375
|
+
}
|
|
376
|
+
lines.push("");
|
|
377
|
+
for (const entity of entities) {
|
|
378
|
+
lines.push(`export interface ${entity.pascalName} {`);
|
|
379
|
+
for (const field of entity.fields) {
|
|
380
|
+
lines.push(` ${field.name}: ${fieldTypeString(field)};`);
|
|
381
|
+
}
|
|
382
|
+
for (const variantName of getVariantFieldNames(entity)) {
|
|
383
|
+
lines.push(` ${variantName}: ${VARIANT_TYPE}[] | null;`);
|
|
384
|
+
}
|
|
385
|
+
for (const rel of entity.relations) {
|
|
386
|
+
if (rel.type === "hasOne") {
|
|
387
|
+
lines.push(
|
|
388
|
+
` ${rel.fieldName}?: { data: ${rel.targetEntityPascalName} } | null;`
|
|
389
|
+
);
|
|
390
|
+
} else {
|
|
391
|
+
lines.push(
|
|
392
|
+
` ${rel.fieldName}?: { data: ${rel.targetEntityPascalName}[]; pagination: PaginationData };`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
lines.push("}", "");
|
|
397
|
+
}
|
|
398
|
+
return lines.join("\n");
|
|
399
|
+
}
|
|
400
|
+
export {
|
|
401
|
+
ENTITY_LIST_QUERY,
|
|
402
|
+
analyzeEntities,
|
|
403
|
+
fetchSchemaWith,
|
|
404
|
+
generateEntitiesFile,
|
|
405
|
+
generateIndexFile
|
|
406
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
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 PyloEventInput {
|
|
42
|
+
event_name: string;
|
|
43
|
+
properties: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
interface PyloEvent {
|
|
46
|
+
event_name: string;
|
|
47
|
+
ts: string;
|
|
48
|
+
properties: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
interface EntityMetadata {
|
|
51
|
+
pascalName: string;
|
|
52
|
+
scalarFieldNames: string[];
|
|
53
|
+
variantFieldNames?: string[];
|
|
54
|
+
jsonFieldNames?: string[];
|
|
55
|
+
enumFields?: Record<string, string[]>;
|
|
56
|
+
relations: Record<string, {
|
|
57
|
+
type: "hasOne" | "hasMany";
|
|
58
|
+
entity: string;
|
|
59
|
+
pascalName: string;
|
|
60
|
+
}>;
|
|
61
|
+
}
|
|
62
|
+
interface SchemaMetadata {
|
|
63
|
+
entities: Record<string, EntityMetadata>;
|
|
64
|
+
unknownFieldBehavior?: "error" | "ignore";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type { EntityMetadata as E, FilterInput as F, PaginationInput as P, QueryInput as Q, SchemaMetadata as S, PaginationData as a, PyloEventInput as b, PyloEvent as c, QueryInputCondition as d, QueryOperator as e, SearchValueInput as f, SortInput as g, SortOrder as h };
|
package/package.json
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pylo/node",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Server-side Pylo SDK with API key authentication",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
-
"import": "./dist/index.js"
|
|
8
|
+
"import": "./dist/index.js",
|
|
9
|
+
"default": "./dist/index.js"
|
|
9
10
|
},
|
|
10
11
|
"./codegen": {
|
|
11
12
|
"types": "./dist/codegen.d.ts",
|
|
12
|
-
"import": "./dist/codegen.js"
|
|
13
|
+
"import": "./dist/codegen.js",
|
|
14
|
+
"default": "./dist/codegen.js"
|
|
15
|
+
},
|
|
16
|
+
"./schema": {
|
|
17
|
+
"types": "./dist/schema.d.ts",
|
|
18
|
+
"import": "./dist/schema.js",
|
|
19
|
+
"default": "./dist/schema.js"
|
|
13
20
|
}
|
|
14
21
|
},
|
|
15
22
|
"main": "dist/index.js",
|
|
@@ -29,7 +36,7 @@
|
|
|
29
36
|
"@types/node": "^22.15.21",
|
|
30
37
|
"tsup": "^8.5.1",
|
|
31
38
|
"typescript": "^5.9.3",
|
|
32
|
-
"@pylo/core": "0.0
|
|
39
|
+
"@pylo/core": "0.1.0",
|
|
33
40
|
"@pylo/auth": "0.0.5"
|
|
34
41
|
},
|
|
35
42
|
"author": "Okeano GmbH",
|