@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.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
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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
|
|
774
|
-
|
|
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);
|