@igorchugurov/public-api-sdk 1.0.0 → 1.2.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/index.mjs CHANGED
@@ -3,6 +3,7 @@ import { createBrowserClient as createBrowserClient$1 } from '@supabase/ssr';
3
3
  var __defProp = Object.defineProperty;
4
4
  var __defProps = Object.defineProperties;
5
5
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
7
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
9
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -19,6 +20,61 @@ var __spreadValues = (a, b) => {
19
20
  return a;
20
21
  };
21
22
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
+ var __esm = (fn, res) => function __init() {
24
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
25
+ };
26
+ var __export = (target, all) => {
27
+ for (var name in all)
28
+ __defProp(target, name, { get: all[name], enumerable: true });
29
+ };
30
+
31
+ // src/utils/slug.ts
32
+ var slug_exports = {};
33
+ __export(slug_exports, {
34
+ generateSlug: () => generateSlug,
35
+ generateUniqueSlugForInstance: () => generateUniqueSlugForInstance,
36
+ validateSlug: () => validateSlug
37
+ });
38
+ function generateSlug(name) {
39
+ if (!name || typeof name !== "string") {
40
+ throw new Error("Name must be a non-empty string");
41
+ }
42
+ return name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").substring(0, 100);
43
+ }
44
+ function validateSlug(slug) {
45
+ if (!slug || typeof slug !== "string") {
46
+ return false;
47
+ }
48
+ if (slug.length === 0 || slug.length > 100) {
49
+ return false;
50
+ }
51
+ const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
52
+ return slugRegex.test(slug);
53
+ }
54
+ function generateRandomSuffix() {
55
+ return Math.random().toString(36).substring(2, 6);
56
+ }
57
+ async function generateUniqueSlugForInstance(name, entityDefinitionId, checkExists, excludeId) {
58
+ const baseSlug = generateSlug(name);
59
+ let slug = baseSlug;
60
+ let attempts = 0;
61
+ const maxAttempts = 100;
62
+ while (attempts < maxAttempts) {
63
+ const exists = await checkExists(slug, entityDefinitionId, excludeId);
64
+ if (!exists) {
65
+ return slug;
66
+ }
67
+ const randomSuffix = generateRandomSuffix();
68
+ slug = `${baseSlug}-${randomSuffix}`;
69
+ attempts++;
70
+ }
71
+ const timestamp = Date.now().toString(36);
72
+ return `${baseSlug}-${timestamp}`;
73
+ }
74
+ var init_slug = __esm({
75
+ "src/utils/slug.ts"() {
76
+ }
77
+ });
22
78
 
23
79
  // src/types/entity-types.ts
24
80
  function isFieldValue(value) {
@@ -395,6 +451,7 @@ var BasePublicAPIClient = class {
395
451
  return {
396
452
  id: row.id,
397
453
  name: row.name,
454
+ slug: row.slug,
398
455
  description: row.description,
399
456
  tableName: row.table_name,
400
457
  type: row.type,
@@ -517,6 +574,38 @@ var BasePublicAPIClient = class {
517
574
  }
518
575
  return config;
519
576
  }
577
+ /**
578
+ * Получить все EntityDefinitions проекта с полями одним запросом (JOIN)
579
+ * Используется для загрузки всех сущностей в layout
580
+ *
581
+ * @returns Массив EntityDefinitionConfig с полями
582
+ */
583
+ async getAllEntityDefinitions() {
584
+ const { data, error } = await this.supabase.from("entity_definition").select(
585
+ `
586
+ *,
587
+ field!field_entity_definition_id_fkey (*)
588
+ `
589
+ ).eq("project_id", this.projectId).order("name");
590
+ if (error) {
591
+ throw new Error(`Failed to load entity definitions: ${error.message}`);
592
+ }
593
+ if (!data || data.length === 0) {
594
+ return [];
595
+ }
596
+ return data.map((row) => {
597
+ const entityDefinition = this.transformEntityDefinitionFromDB(row);
598
+ const fields = (row.field || []).map(
599
+ (fieldRow) => this.transformFieldFromDB(fieldRow)
600
+ );
601
+ fields.sort(
602
+ (a, b) => a.displayIndex - b.displayIndex
603
+ );
604
+ return __spreadProps(__spreadValues({}, entityDefinition), {
605
+ fields
606
+ });
607
+ });
608
+ }
520
609
  /**
521
610
  * Преобразование EntityDefinitionConfig в EntityDefinition
522
611
  */
@@ -524,6 +613,7 @@ var BasePublicAPIClient = class {
524
613
  return {
525
614
  id: config.id,
526
615
  name: config.name,
616
+ slug: config.slug,
527
617
  description: config.description,
528
618
  tableName: config.tableName,
529
619
  type: config.type,
@@ -635,6 +725,7 @@ var BasePublicAPIClient = class {
635
725
  function transformEntityInstance(row) {
636
726
  return {
637
727
  id: row.id,
728
+ slug: row.slug,
638
729
  entityDefinitionId: row.entity_definition_id,
639
730
  projectId: row.project_id,
640
731
  data: row.data || {},
@@ -645,6 +736,7 @@ function transformEntityInstance(row) {
645
736
  function flattenInstance(instance, fields, relationsAsIds = false) {
646
737
  const result = {
647
738
  id: instance.id,
739
+ slug: instance.slug,
648
740
  entityDefinitionId: instance.entityDefinitionId,
649
741
  projectId: instance.projectId,
650
742
  createdAt: instance.createdAt,
@@ -673,6 +765,7 @@ function flattenInstance(instance, fields, relationsAsIds = false) {
673
765
  }
674
766
 
675
767
  // src/client.ts
768
+ init_slug();
676
769
  var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
677
770
  constructor(supabase, projectId, mode, options = {}) {
678
771
  super(supabase, projectId, options);
@@ -824,6 +917,134 @@ var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
824
917
  handleSupabaseError(error);
825
918
  }
826
919
  }
920
+ /**
921
+ * Получить один экземпляр по slug
922
+ */
923
+ async getInstanceBySlug(entityDefinitionId, slug, params) {
924
+ var _a;
925
+ try {
926
+ if (!validateSlug(slug)) {
927
+ throw new ValidationError(
928
+ "slug",
929
+ "Slug must contain only lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen"
930
+ );
931
+ }
932
+ const fields = await this.getFields(entityDefinitionId);
933
+ const { data: instance, error: instanceError } = await this.supabase.from("entity_instance").select("*").eq("slug", slug).eq("entity_definition_id", entityDefinitionId).eq("project_id", this.projectId).single();
934
+ if (instanceError || !instance) {
935
+ handleInstanceError(
936
+ instanceError || new Error("Instance not found"),
937
+ slug
938
+ );
939
+ }
940
+ const transformedInstance = transformEntityInstance(instance);
941
+ if (transformedInstance.entityDefinitionId !== entityDefinitionId) {
942
+ throw new NotFoundError("Entity instance", slug);
943
+ }
944
+ const relationFields = fields.filter(
945
+ (f) => f.dbType === "manyToMany" || f.dbType === "manyToOne" || f.dbType === "oneToMany" || f.dbType === "oneToOne"
946
+ );
947
+ const relations = {};
948
+ if (relationFields.length > 0) {
949
+ const relationFieldIds = relationFields.map((f) => f.id).filter((id) => Boolean(id));
950
+ if (relationFieldIds.length > 0) {
951
+ const { data: allRelations, error: relationsError } = await this.supabase.from("entity_relation").select("target_instance_id, relation_field_id").eq("source_instance_id", transformedInstance.id).in("relation_field_id", relationFieldIds);
952
+ if (relationsError) {
953
+ throw new SDKError(
954
+ "RELATIONS_LOAD_ERROR",
955
+ `Failed to load relations for instance with slug ${slug}: ${relationsError.message}`,
956
+ 500,
957
+ relationsError
958
+ );
959
+ }
960
+ if (allRelations && allRelations.length > 0) {
961
+ const targetInstanceIds = [
962
+ ...new Set(allRelations.map((r) => r.target_instance_id))
963
+ ];
964
+ const { data: relatedInstances, error: instancesError } = await this.supabase.from("entity_instance").select("*").in("id", targetInstanceIds);
965
+ if (instancesError) {
966
+ throw new SDKError(
967
+ "RELATED_INSTANCES_LOAD_ERROR",
968
+ `Failed to load related instances for instance with slug ${slug}: ${instancesError.message}`,
969
+ 500,
970
+ instancesError
971
+ );
972
+ }
973
+ if (relatedInstances) {
974
+ const relatedInstancesMap = new Map(
975
+ relatedInstances.map((inst) => [
976
+ inst.id,
977
+ transformEntityInstance(inst)
978
+ ])
979
+ );
980
+ for (const relation of allRelations) {
981
+ const relationField = relationFields.find(
982
+ (f) => f.id === relation.relation_field_id
983
+ );
984
+ if (relationField) {
985
+ const relatedInstance = relatedInstancesMap.get(
986
+ relation.target_instance_id
987
+ );
988
+ if (relatedInstance) {
989
+ if (!relations[relationField.name]) {
990
+ relations[relationField.name] = [];
991
+ }
992
+ relations[relationField.name].push(
993
+ relatedInstance
994
+ );
995
+ }
996
+ }
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+ const fileFields = fields.filter(
1003
+ (f) => f.type === "files" || f.type === "images"
1004
+ );
1005
+ if (fileFields.length > 0) {
1006
+ const { data: allFiles, error: filesError } = await this.supabase.from("entity_file").select("id, field_id").eq("entity_instance_id", transformedInstance.id);
1007
+ if (filesError) {
1008
+ throw new SDKError(
1009
+ "FILES_LOAD_ERROR",
1010
+ `Failed to load files for instance with slug ${slug}: ${filesError.message}`,
1011
+ 500,
1012
+ filesError
1013
+ );
1014
+ }
1015
+ if (allFiles) {
1016
+ const filesByFieldId = /* @__PURE__ */ new Map();
1017
+ allFiles.forEach((file) => {
1018
+ if (file.field_id) {
1019
+ if (!filesByFieldId.has(file.field_id)) {
1020
+ filesByFieldId.set(file.field_id, []);
1021
+ }
1022
+ filesByFieldId.get(file.field_id).push(file.id);
1023
+ }
1024
+ });
1025
+ fileFields.forEach((field) => {
1026
+ const fileIds = filesByFieldId.get(field.id) || [];
1027
+ if (fileIds.length > 0 || !transformedInstance.data[field.name]) {
1028
+ transformedInstance.data[field.name] = fileIds;
1029
+ }
1030
+ });
1031
+ }
1032
+ }
1033
+ const instanceWithRelations = __spreadProps(__spreadValues({}, transformedInstance), {
1034
+ relations: Object.keys(relations).length > 0 ? relations : void 0
1035
+ });
1036
+ return flattenInstance(
1037
+ instanceWithRelations,
1038
+ fields.map((f) => ({ name: f.name, dbType: f.dbType })),
1039
+ (_a = params == null ? void 0 : params.relationsAsIds) != null ? _a : false
1040
+ );
1041
+ } catch (error) {
1042
+ if (error instanceof NotFoundError || error instanceof PermissionDeniedError || error instanceof ValidationError || error instanceof SDKError) {
1043
+ throw error;
1044
+ }
1045
+ handleSupabaseError(error);
1046
+ }
1047
+ }
827
1048
  /**
828
1049
  * Получить список экземпляров
829
1050
  * Поддерживает поиск, фильтры (JSONB и relation), пагинацию
@@ -1212,9 +1433,27 @@ var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
1212
1433
  );
1213
1434
  }
1214
1435
  const { data: instanceData, relations } = data;
1436
+ const name = instanceData.name;
1437
+ if (!name || typeof name !== "string") {
1438
+ throw new Error(
1439
+ "Field 'name' is required and must be a string for slug generation"
1440
+ );
1441
+ }
1442
+ const {
1443
+ generateUniqueSlugForInstance: generateUniqueSlugForInstance2
1444
+ } = await Promise.resolve().then(() => (init_slug(), slug_exports));
1445
+ const slug = await generateUniqueSlugForInstance2(
1446
+ name,
1447
+ entityDefinitionId,
1448
+ async (slugToCheck, entityDefIdToCheck) => {
1449
+ const { data: existing } = await this.supabase.from("entity_instance").select("id").eq("entity_definition_id", entityDefIdToCheck).eq("slug", slugToCheck).single();
1450
+ return !!existing;
1451
+ }
1452
+ );
1215
1453
  const { data: instance, error: instanceError } = await this.supabase.from("entity_instance").insert({
1216
1454
  entity_definition_id: entityDefinitionId,
1217
1455
  project_id: this.projectId,
1456
+ slug,
1218
1457
  data: instanceData,
1219
1458
  created_by: (user == null ? void 0 : user.id) || null
1220
1459
  }).select().single();