@pylo/node 0.0.12 → 0.1.1

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 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 fetchSchema(config) {
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 graphqlRequestWithApiKey(
134
- config.endpoint,
135
- ENTITY_LIST_QUERY,
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,13 +146,18 @@ 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",
156
157
  RICHTEXT: "string",
157
158
  INT: "number",
158
159
  FLOAT: "number",
159
- JSON: "Record<string, unknown> | unknown[]",
160
+ JSON: "string",
160
161
  DATE: "string",
161
162
  DATETIME: "string",
162
163
  TIME: "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(` ${rel.fieldName}?: { data: ${rel.targetEntityPascalName} } | null;`);
449
+ lines.push(
450
+ ` ${rel.fieldName}?: { data: ${rel.targetEntityPascalName} } | null;`
451
+ );
429
452
  } else {
430
- lines.push(` ${rel.fieldName}?: { data: ${rel.targetEntityPascalName}[]; pagination: PaginationData };`);
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
@@ -1 +1,2 @@
1
1
  export { PyloConfig, defineConfig } from '@pylo/core/codegen';
2
+ import '@pylo/core/schema';
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, true> : OmitNever<{
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> ? true | {
138
- select?: EntitySelect<S, Target, Increment[Depth]>;
121
+ } ? Target extends EntityName<S> ? {
122
+ select: EntitySelect<S, Target, Increment[Depth]>;
139
123
  filter?: FilterInput;
140
- } : true : EntityRelations<S, E>[K] extends {
124
+ } : never : EntityRelations<S, E>[K] extends {
141
125
  type: "hasMany";
142
126
  entity: infer Target;
143
- } ? Target extends EntityName<S> ? true | {
144
- select?: EntitySelect<S, Target, Increment[Depth]>;
127
+ } ? Target extends EntityName<S> ? {
128
+ select: EntitySelect<S, Target, Increment[Depth]>;
145
129
  filter?: FilterInput;
146
130
  pagination?: PaginationInput;
147
- } : true : true;
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> | 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>;
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> | undefined = undefined> = {
186
- select?: Sel extends undefined ? EntitySelect<S, E> : StrictSelect<Sel, EntitySelect<S, E>>;
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> | undefined = undefined> = {
191
- select?: Sel extends undefined ? EntitySelect<S, E> : StrictSelect<Sel, EntitySelect<S, E>>;
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> | undefined = undefined>(options?: ListOptions<S, E, Sel> & RequestOptions): Promise<ListResult<EntityResult<S, E, Sel>>>;
220
- byId<Sel extends EntitySelect<S, E> | undefined = undefined>(id: string, options?: ByIdOptions<S, E, Sel> & RequestOptions): Promise<EntityResult<S, E, Sel> | null>;
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 EntityMetadata, 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, PyloError, type PyloEvent, type PyloEventFieldValue, type PyloEventFieldValuesOptions, type PyloEventInput, type PyloEventListResult, type PyloEventProperty, type PyloEventPropertyKeysOptions, type QueryInput, type QueryInputCondition, type QueryOperator, type RequestOptions, type SchemaMetadata, type SearchValueInput, type SortInput, type SortOrder, type StrictSelect, type TimeBucketInput, type UpsertInput, createPyloNode };
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, entityMeta, schemaMetadata, variables, variableTypes, prefix) {
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 (entityMeta.scalarFieldNames.includes(key)) {
71
- if (value === true) {
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 (variantFieldNames.includes(key)) {
77
- if (value === true) {
78
- fields.push(`${key} { data { value variant } }`);
79
- }
80
- continue;
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 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
- );
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 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
- }
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 buildListQuery(entityKey, options, schemaMetadata) {
140
- const entityMeta = schemaMetadata.entities[entityKey];
141
- if (!entityMeta) {
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, schemaMetadata) {
184
- const entityMeta = schemaMetadata.entities[entityKey];
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, metadata, auth, globalHeaders) {
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 entityMeta = metadata.entities[entityKey];
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
- entityMeta.pascalName,
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${entityMeta.pascalName}`;
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 entityMeta = metadata.entities[entityKey];
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
- entityMeta.pascalName,
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${entityMeta.pascalName}`;
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, metadata, auth, globalHeaders);
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
  };
@@ -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: "string",
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.12",
3
+ "version": "0.1.1",
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,8 +36,8 @@
29
36
  "@types/node": "^22.15.21",
30
37
  "tsup": "^8.5.1",
31
38
  "typescript": "^5.9.3",
32
- "@pylo/core": "0.0.14",
33
- "@pylo/auth": "0.0.5"
39
+ "@pylo/auth": "0.0.5",
40
+ "@pylo/core": "0.1.1"
34
41
  },
35
42
  "author": "Okeano GmbH",
36
43
  "license": "MIT",