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