@igorchugurov/public-api-sdk 1.1.0 → 1.3.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/server.mjs CHANGED
@@ -32,7 +32,8 @@ var __export = (target, all) => {
32
32
  var slug_exports = {};
33
33
  __export(slug_exports, {
34
34
  generateSlug: () => generateSlug,
35
- generateUniqueSlugForInstance: () => generateUniqueSlugForInstance
35
+ generateUniqueSlugForInstance: () => generateUniqueSlugForInstance,
36
+ validateSlug: () => validateSlug
36
37
  });
37
38
  function generateSlug(name) {
38
39
  if (!name || typeof name !== "string") {
@@ -40,6 +41,16 @@ function generateSlug(name) {
40
41
  }
41
42
  return name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").substring(0, 100);
42
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
+ }
43
54
  function generateRandomSuffix() {
44
55
  return Math.random().toString(36).substring(2, 6);
45
56
  }
@@ -581,6 +592,25 @@ function flattenInstance(instance, fields, relationsAsIds = false) {
581
592
  }
582
593
  return result;
583
594
  }
595
+ function transformEntityFile(row) {
596
+ return {
597
+ id: row.id,
598
+ entityInstanceId: row.entity_instance_id,
599
+ fieldId: row.field_id,
600
+ fileUrl: row.file_url,
601
+ filePath: row.file_path,
602
+ fileName: row.file_name,
603
+ fileSize: row.file_size,
604
+ fileType: row.file_type,
605
+ storageBucket: row.storage_bucket,
606
+ uploadedBy: row.uploaded_by,
607
+ createdAt: row.created_at,
608
+ updatedAt: row.updated_at
609
+ };
610
+ }
611
+
612
+ // src/client.ts
613
+ init_slug();
584
614
 
585
615
  // src/errors.ts
586
616
  var SDKError = class extends Error {
@@ -610,6 +640,19 @@ var PermissionDeniedError = class extends SDKError {
610
640
  );
611
641
  }
612
642
  };
643
+ var ValidationError = class extends SDKError {
644
+ constructor(field, message) {
645
+ super(
646
+ "VALIDATION_ERROR",
647
+ `Validation failed for ${field}: ${message}`,
648
+ 400,
649
+ {
650
+ field,
651
+ message
652
+ }
653
+ );
654
+ }
655
+ };
613
656
  function handleSupabaseError(error) {
614
657
  if (error.code === "PGRST116") {
615
658
  throw new NotFoundError("Resource");
@@ -670,6 +713,42 @@ var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
670
713
  getMode() {
671
714
  return this.mode;
672
715
  }
716
+ /**
717
+ * Загружает файлы для экземпляра и возвращает их как полные объекты EntityFile,
718
+ * сгруппированные по именам полей
719
+ */
720
+ async loadFilesForInstance(instanceId, fileFields) {
721
+ if (fileFields.length === 0) {
722
+ return /* @__PURE__ */ new Map();
723
+ }
724
+ const { data: allFiles, error: filesError } = await this.supabase.from("entity_file").select("*").eq("entity_instance_id", instanceId);
725
+ if (filesError) {
726
+ throw new SDKError(
727
+ "FILES_LOAD_ERROR",
728
+ `Failed to load files for instance ${instanceId}: ${filesError.message}`,
729
+ 500,
730
+ filesError
731
+ );
732
+ }
733
+ if (!allFiles || allFiles.length === 0) {
734
+ return /* @__PURE__ */ new Map();
735
+ }
736
+ const filesMap = /* @__PURE__ */ new Map();
737
+ const fieldIdToName = new Map(
738
+ fileFields.map((f) => [f.id, f.name])
739
+ );
740
+ allFiles.forEach((fileRow) => {
741
+ if (!fileRow.field_id) return;
742
+ const fieldName = fieldIdToName.get(fileRow.field_id);
743
+ if (!fieldName) return;
744
+ const entityFile = transformEntityFile(fileRow);
745
+ if (!filesMap.has(fieldName)) {
746
+ filesMap.set(fieldName, []);
747
+ }
748
+ filesMap.get(fieldName).push(entityFile);
749
+ });
750
+ return filesMap;
751
+ }
673
752
  /**
674
753
  * Получить один экземпляр
675
754
  */
@@ -746,34 +825,127 @@ var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
746
825
  }
747
826
  }
748
827
  }
749
- const fileFields = fields.filter(
750
- (f) => f.type === "files" || f.type === "images"
751
- );
752
- if (fileFields.length > 0) {
753
- const { data: allFiles, error: filesError } = await this.supabase.from("entity_file").select("id, field_id").eq("entity_instance_id", id);
754
- if (filesError) {
755
- throw new SDKError(
756
- "FILES_LOAD_ERROR",
757
- `Failed to load files for instance ${id}: ${filesError.message}`,
758
- 500,
759
- filesError
760
- );
828
+ if ((params == null ? void 0 : params.loadFiles) === true) {
829
+ const fileFields = fields.filter(
830
+ (f) => f.type === "files" || f.type === "images"
831
+ );
832
+ if (fileFields.length > 0) {
833
+ const filesMap = await this.loadFilesForInstance(id, fileFields);
834
+ fileFields.forEach((field) => {
835
+ const files = filesMap.get(field.name) || [];
836
+ transformedInstance.data[field.name] = files;
837
+ });
761
838
  }
762
- if (allFiles) {
763
- const filesByFieldId = /* @__PURE__ */ new Map();
764
- allFiles.forEach((file) => {
765
- if (file.field_id) {
766
- if (!filesByFieldId.has(file.field_id)) {
767
- filesByFieldId.set(file.field_id, []);
839
+ }
840
+ const instanceWithRelations = __spreadProps(__spreadValues({}, transformedInstance), {
841
+ relations: Object.keys(relations).length > 0 ? relations : void 0
842
+ });
843
+ return flattenInstance(
844
+ instanceWithRelations,
845
+ fields.map((f) => ({ name: f.name, dbType: f.dbType })),
846
+ (_a = params == null ? void 0 : params.relationsAsIds) != null ? _a : false
847
+ );
848
+ } catch (error) {
849
+ if (error instanceof NotFoundError || error instanceof PermissionDeniedError || error instanceof SDKError) {
850
+ throw error;
851
+ }
852
+ handleSupabaseError(error);
853
+ }
854
+ }
855
+ /**
856
+ * Получить один экземпляр по slug
857
+ */
858
+ async getInstanceBySlug(entityDefinitionId, slug, params) {
859
+ var _a;
860
+ try {
861
+ if (!validateSlug(slug)) {
862
+ throw new ValidationError(
863
+ "slug",
864
+ "Slug must contain only lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen"
865
+ );
866
+ }
867
+ const fields = await this.getFields(entityDefinitionId);
868
+ 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();
869
+ if (instanceError || !instance) {
870
+ handleInstanceError(
871
+ instanceError || new Error("Instance not found"),
872
+ slug
873
+ );
874
+ }
875
+ const transformedInstance = transformEntityInstance(instance);
876
+ if (transformedInstance.entityDefinitionId !== entityDefinitionId) {
877
+ throw new NotFoundError("Entity instance", slug);
878
+ }
879
+ const relationFields = fields.filter(
880
+ (f) => f.dbType === "manyToMany" || f.dbType === "manyToOne" || f.dbType === "oneToMany" || f.dbType === "oneToOne"
881
+ );
882
+ const relations = {};
883
+ if (relationFields.length > 0) {
884
+ const relationFieldIds = relationFields.map((f) => f.id).filter((id) => Boolean(id));
885
+ if (relationFieldIds.length > 0) {
886
+ 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);
887
+ if (relationsError) {
888
+ throw new SDKError(
889
+ "RELATIONS_LOAD_ERROR",
890
+ `Failed to load relations for instance with slug ${slug}: ${relationsError.message}`,
891
+ 500,
892
+ relationsError
893
+ );
894
+ }
895
+ if (allRelations && allRelations.length > 0) {
896
+ const targetInstanceIds = [
897
+ ...new Set(allRelations.map((r) => r.target_instance_id))
898
+ ];
899
+ const { data: relatedInstances, error: instancesError } = await this.supabase.from("entity_instance").select("*").in("id", targetInstanceIds);
900
+ if (instancesError) {
901
+ throw new SDKError(
902
+ "RELATED_INSTANCES_LOAD_ERROR",
903
+ `Failed to load related instances for instance with slug ${slug}: ${instancesError.message}`,
904
+ 500,
905
+ instancesError
906
+ );
907
+ }
908
+ if (relatedInstances) {
909
+ const relatedInstancesMap = new Map(
910
+ relatedInstances.map((inst) => [
911
+ inst.id,
912
+ transformEntityInstance(inst)
913
+ ])
914
+ );
915
+ for (const relation of allRelations) {
916
+ const relationField = relationFields.find(
917
+ (f) => f.id === relation.relation_field_id
918
+ );
919
+ if (relationField) {
920
+ const relatedInstance = relatedInstancesMap.get(
921
+ relation.target_instance_id
922
+ );
923
+ if (relatedInstance) {
924
+ if (!relations[relationField.name]) {
925
+ relations[relationField.name] = [];
926
+ }
927
+ relations[relationField.name].push(
928
+ relatedInstance
929
+ );
930
+ }
931
+ }
768
932
  }
769
- filesByFieldId.get(file.field_id).push(file.id);
770
933
  }
771
- });
934
+ }
935
+ }
936
+ }
937
+ if ((params == null ? void 0 : params.loadFiles) === true) {
938
+ const fileFields = fields.filter(
939
+ (f) => f.type === "files" || f.type === "images"
940
+ );
941
+ if (fileFields.length > 0) {
942
+ const filesMap = await this.loadFilesForInstance(
943
+ transformedInstance.id,
944
+ fileFields
945
+ );
772
946
  fileFields.forEach((field) => {
773
- const fileIds = filesByFieldId.get(field.id) || [];
774
- if (fileIds.length > 0 || !transformedInstance.data[field.name]) {
775
- transformedInstance.data[field.name] = fileIds;
776
- }
947
+ const files = filesMap.get(field.name) || [];
948
+ transformedInstance.data[field.name] = files;
777
949
  });
778
950
  }
779
951
  }
@@ -786,7 +958,7 @@ var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
786
958
  (_a = params == null ? void 0 : params.relationsAsIds) != null ? _a : false
787
959
  );
788
960
  } catch (error) {
789
- if (error instanceof NotFoundError || error instanceof PermissionDeniedError || error instanceof SDKError) {
961
+ if (error instanceof NotFoundError || error instanceof PermissionDeniedError || error instanceof ValidationError || error instanceof SDKError) {
790
962
  throw error;
791
963
  }
792
964
  handleSupabaseError(error);