@pylo/node 0.0.11 → 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/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.11",
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,8 +36,8 @@
29
36
  "@types/node": "^22.15.21",
30
37
  "tsup": "^8.5.1",
31
38
  "typescript": "^5.9.3",
32
- "@pylo/auth": "0.0.5",
33
- "@pylo/core": "0.0.13"
39
+ "@pylo/core": "0.1.0",
40
+ "@pylo/auth": "0.0.5"
34
41
  },
35
42
  "author": "Okeano GmbH",
36
43
  "license": "MIT",