@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/README.md +127 -3
- package/dist/{client-DqqjGYgA.d.mts → client-DS5xnLAo.d.mts} +20 -0
- package/dist/{client-DqqjGYgA.d.ts → client-DS5xnLAo.d.ts} +20 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +239 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +239 -0
- 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 +254 -0
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +254 -0
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server.d.mts
CHANGED
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -5,6 +5,7 @@ var ssr = require('@supabase/ssr');
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __defProps = Object.defineProperties;
|
|
7
7
|
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
8
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
9
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
9
10
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
11
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
@@ -21,6 +22,61 @@ var __spreadValues = (a, b) => {
|
|
|
21
22
|
return a;
|
|
22
23
|
};
|
|
23
24
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
25
|
+
var __esm = (fn, res) => function __init() {
|
|
26
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
27
|
+
};
|
|
28
|
+
var __export = (target, all) => {
|
|
29
|
+
for (var name in all)
|
|
30
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/utils/slug.ts
|
|
34
|
+
var slug_exports = {};
|
|
35
|
+
__export(slug_exports, {
|
|
36
|
+
generateSlug: () => generateSlug,
|
|
37
|
+
generateUniqueSlugForInstance: () => generateUniqueSlugForInstance,
|
|
38
|
+
validateSlug: () => validateSlug
|
|
39
|
+
});
|
|
40
|
+
function generateSlug(name) {
|
|
41
|
+
if (!name || typeof name !== "string") {
|
|
42
|
+
throw new Error("Name must be a non-empty string");
|
|
43
|
+
}
|
|
44
|
+
return name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").substring(0, 100);
|
|
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
|
+
}
|
|
56
|
+
function generateRandomSuffix() {
|
|
57
|
+
return Math.random().toString(36).substring(2, 6);
|
|
58
|
+
}
|
|
59
|
+
async function generateUniqueSlugForInstance(name, entityDefinitionId, checkExists, excludeId) {
|
|
60
|
+
const baseSlug = generateSlug(name);
|
|
61
|
+
let slug = baseSlug;
|
|
62
|
+
let attempts = 0;
|
|
63
|
+
const maxAttempts = 100;
|
|
64
|
+
while (attempts < maxAttempts) {
|
|
65
|
+
const exists = await checkExists(slug, entityDefinitionId, excludeId);
|
|
66
|
+
if (!exists) {
|
|
67
|
+
return slug;
|
|
68
|
+
}
|
|
69
|
+
const randomSuffix = generateRandomSuffix();
|
|
70
|
+
slug = `${baseSlug}-${randomSuffix}`;
|
|
71
|
+
attempts++;
|
|
72
|
+
}
|
|
73
|
+
const timestamp = Date.now().toString(36);
|
|
74
|
+
return `${baseSlug}-${timestamp}`;
|
|
75
|
+
}
|
|
76
|
+
var init_slug = __esm({
|
|
77
|
+
"src/utils/slug.ts"() {
|
|
78
|
+
}
|
|
79
|
+
});
|
|
24
80
|
function createServerClient(supabaseUrl, supabaseAnonKey, options) {
|
|
25
81
|
return ssr.createServerClient(supabaseUrl, supabaseAnonKey, {
|
|
26
82
|
cookies: (options == null ? void 0 : options.cookies) || {
|
|
@@ -226,6 +282,7 @@ var BasePublicAPIClient = class {
|
|
|
226
282
|
return {
|
|
227
283
|
id: row.id,
|
|
228
284
|
name: row.name,
|
|
285
|
+
slug: row.slug,
|
|
229
286
|
description: row.description,
|
|
230
287
|
tableName: row.table_name,
|
|
231
288
|
type: row.type,
|
|
@@ -348,6 +405,38 @@ var BasePublicAPIClient = class {
|
|
|
348
405
|
}
|
|
349
406
|
return config;
|
|
350
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Получить все EntityDefinitions проекта с полями одним запросом (JOIN)
|
|
410
|
+
* Используется для загрузки всех сущностей в layout
|
|
411
|
+
*
|
|
412
|
+
* @returns Массив EntityDefinitionConfig с полями
|
|
413
|
+
*/
|
|
414
|
+
async getAllEntityDefinitions() {
|
|
415
|
+
const { data, error } = await this.supabase.from("entity_definition").select(
|
|
416
|
+
`
|
|
417
|
+
*,
|
|
418
|
+
field!field_entity_definition_id_fkey (*)
|
|
419
|
+
`
|
|
420
|
+
).eq("project_id", this.projectId).order("name");
|
|
421
|
+
if (error) {
|
|
422
|
+
throw new Error(`Failed to load entity definitions: ${error.message}`);
|
|
423
|
+
}
|
|
424
|
+
if (!data || data.length === 0) {
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
return data.map((row) => {
|
|
428
|
+
const entityDefinition = this.transformEntityDefinitionFromDB(row);
|
|
429
|
+
const fields = (row.field || []).map(
|
|
430
|
+
(fieldRow) => this.transformFieldFromDB(fieldRow)
|
|
431
|
+
);
|
|
432
|
+
fields.sort(
|
|
433
|
+
(a, b) => a.displayIndex - b.displayIndex
|
|
434
|
+
);
|
|
435
|
+
return __spreadProps(__spreadValues({}, entityDefinition), {
|
|
436
|
+
fields
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
}
|
|
351
440
|
/**
|
|
352
441
|
* Преобразование EntityDefinitionConfig в EntityDefinition
|
|
353
442
|
*/
|
|
@@ -355,6 +444,7 @@ var BasePublicAPIClient = class {
|
|
|
355
444
|
return {
|
|
356
445
|
id: config.id,
|
|
357
446
|
name: config.name,
|
|
447
|
+
slug: config.slug,
|
|
358
448
|
description: config.description,
|
|
359
449
|
tableName: config.tableName,
|
|
360
450
|
type: config.type,
|
|
@@ -466,6 +556,7 @@ var BasePublicAPIClient = class {
|
|
|
466
556
|
function transformEntityInstance(row) {
|
|
467
557
|
return {
|
|
468
558
|
id: row.id,
|
|
559
|
+
slug: row.slug,
|
|
469
560
|
entityDefinitionId: row.entity_definition_id,
|
|
470
561
|
projectId: row.project_id,
|
|
471
562
|
data: row.data || {},
|
|
@@ -476,6 +567,7 @@ function transformEntityInstance(row) {
|
|
|
476
567
|
function flattenInstance(instance, fields, relationsAsIds = false) {
|
|
477
568
|
const result = {
|
|
478
569
|
id: instance.id,
|
|
570
|
+
slug: instance.slug,
|
|
479
571
|
entityDefinitionId: instance.entityDefinitionId,
|
|
480
572
|
projectId: instance.projectId,
|
|
481
573
|
createdAt: instance.createdAt,
|
|
@@ -503,6 +595,9 @@ function flattenInstance(instance, fields, relationsAsIds = false) {
|
|
|
503
595
|
return result;
|
|
504
596
|
}
|
|
505
597
|
|
|
598
|
+
// src/client.ts
|
|
599
|
+
init_slug();
|
|
600
|
+
|
|
506
601
|
// src/errors.ts
|
|
507
602
|
var SDKError = class extends Error {
|
|
508
603
|
constructor(code, message, statusCode, details) {
|
|
@@ -531,6 +626,19 @@ var PermissionDeniedError = class extends SDKError {
|
|
|
531
626
|
);
|
|
532
627
|
}
|
|
533
628
|
};
|
|
629
|
+
var ValidationError = class extends SDKError {
|
|
630
|
+
constructor(field, message) {
|
|
631
|
+
super(
|
|
632
|
+
"VALIDATION_ERROR",
|
|
633
|
+
`Validation failed for ${field}: ${message}`,
|
|
634
|
+
400,
|
|
635
|
+
{
|
|
636
|
+
field,
|
|
637
|
+
message
|
|
638
|
+
}
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
};
|
|
534
642
|
function handleSupabaseError(error) {
|
|
535
643
|
if (error.code === "PGRST116") {
|
|
536
644
|
throw new NotFoundError("Resource");
|
|
@@ -713,6 +821,134 @@ var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
|
|
|
713
821
|
handleSupabaseError(error);
|
|
714
822
|
}
|
|
715
823
|
}
|
|
824
|
+
/**
|
|
825
|
+
* Получить один экземпляр по slug
|
|
826
|
+
*/
|
|
827
|
+
async getInstanceBySlug(entityDefinitionId, slug, params) {
|
|
828
|
+
var _a;
|
|
829
|
+
try {
|
|
830
|
+
if (!validateSlug(slug)) {
|
|
831
|
+
throw new ValidationError(
|
|
832
|
+
"slug",
|
|
833
|
+
"Slug must contain only lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen"
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
const fields = await this.getFields(entityDefinitionId);
|
|
837
|
+
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();
|
|
838
|
+
if (instanceError || !instance) {
|
|
839
|
+
handleInstanceError(
|
|
840
|
+
instanceError || new Error("Instance not found"),
|
|
841
|
+
slug
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
const transformedInstance = transformEntityInstance(instance);
|
|
845
|
+
if (transformedInstance.entityDefinitionId !== entityDefinitionId) {
|
|
846
|
+
throw new NotFoundError("Entity instance", slug);
|
|
847
|
+
}
|
|
848
|
+
const relationFields = fields.filter(
|
|
849
|
+
(f) => f.dbType === "manyToMany" || f.dbType === "manyToOne" || f.dbType === "oneToMany" || f.dbType === "oneToOne"
|
|
850
|
+
);
|
|
851
|
+
const relations = {};
|
|
852
|
+
if (relationFields.length > 0) {
|
|
853
|
+
const relationFieldIds = relationFields.map((f) => f.id).filter((id) => Boolean(id));
|
|
854
|
+
if (relationFieldIds.length > 0) {
|
|
855
|
+
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);
|
|
856
|
+
if (relationsError) {
|
|
857
|
+
throw new SDKError(
|
|
858
|
+
"RELATIONS_LOAD_ERROR",
|
|
859
|
+
`Failed to load relations for instance with slug ${slug}: ${relationsError.message}`,
|
|
860
|
+
500,
|
|
861
|
+
relationsError
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
if (allRelations && allRelations.length > 0) {
|
|
865
|
+
const targetInstanceIds = [
|
|
866
|
+
...new Set(allRelations.map((r) => r.target_instance_id))
|
|
867
|
+
];
|
|
868
|
+
const { data: relatedInstances, error: instancesError } = await this.supabase.from("entity_instance").select("*").in("id", targetInstanceIds);
|
|
869
|
+
if (instancesError) {
|
|
870
|
+
throw new SDKError(
|
|
871
|
+
"RELATED_INSTANCES_LOAD_ERROR",
|
|
872
|
+
`Failed to load related instances for instance with slug ${slug}: ${instancesError.message}`,
|
|
873
|
+
500,
|
|
874
|
+
instancesError
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
if (relatedInstances) {
|
|
878
|
+
const relatedInstancesMap = new Map(
|
|
879
|
+
relatedInstances.map((inst) => [
|
|
880
|
+
inst.id,
|
|
881
|
+
transformEntityInstance(inst)
|
|
882
|
+
])
|
|
883
|
+
);
|
|
884
|
+
for (const relation of allRelations) {
|
|
885
|
+
const relationField = relationFields.find(
|
|
886
|
+
(f) => f.id === relation.relation_field_id
|
|
887
|
+
);
|
|
888
|
+
if (relationField) {
|
|
889
|
+
const relatedInstance = relatedInstancesMap.get(
|
|
890
|
+
relation.target_instance_id
|
|
891
|
+
);
|
|
892
|
+
if (relatedInstance) {
|
|
893
|
+
if (!relations[relationField.name]) {
|
|
894
|
+
relations[relationField.name] = [];
|
|
895
|
+
}
|
|
896
|
+
relations[relationField.name].push(
|
|
897
|
+
relatedInstance
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const fileFields = fields.filter(
|
|
907
|
+
(f) => f.type === "files" || f.type === "images"
|
|
908
|
+
);
|
|
909
|
+
if (fileFields.length > 0) {
|
|
910
|
+
const { data: allFiles, error: filesError } = await this.supabase.from("entity_file").select("id, field_id").eq("entity_instance_id", transformedInstance.id);
|
|
911
|
+
if (filesError) {
|
|
912
|
+
throw new SDKError(
|
|
913
|
+
"FILES_LOAD_ERROR",
|
|
914
|
+
`Failed to load files for instance with slug ${slug}: ${filesError.message}`,
|
|
915
|
+
500,
|
|
916
|
+
filesError
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
if (allFiles) {
|
|
920
|
+
const filesByFieldId = /* @__PURE__ */ new Map();
|
|
921
|
+
allFiles.forEach((file) => {
|
|
922
|
+
if (file.field_id) {
|
|
923
|
+
if (!filesByFieldId.has(file.field_id)) {
|
|
924
|
+
filesByFieldId.set(file.field_id, []);
|
|
925
|
+
}
|
|
926
|
+
filesByFieldId.get(file.field_id).push(file.id);
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
fileFields.forEach((field) => {
|
|
930
|
+
const fileIds = filesByFieldId.get(field.id) || [];
|
|
931
|
+
if (fileIds.length > 0 || !transformedInstance.data[field.name]) {
|
|
932
|
+
transformedInstance.data[field.name] = fileIds;
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
const instanceWithRelations = __spreadProps(__spreadValues({}, transformedInstance), {
|
|
938
|
+
relations: Object.keys(relations).length > 0 ? relations : void 0
|
|
939
|
+
});
|
|
940
|
+
return flattenInstance(
|
|
941
|
+
instanceWithRelations,
|
|
942
|
+
fields.map((f) => ({ name: f.name, dbType: f.dbType })),
|
|
943
|
+
(_a = params == null ? void 0 : params.relationsAsIds) != null ? _a : false
|
|
944
|
+
);
|
|
945
|
+
} catch (error) {
|
|
946
|
+
if (error instanceof NotFoundError || error instanceof PermissionDeniedError || error instanceof ValidationError || error instanceof SDKError) {
|
|
947
|
+
throw error;
|
|
948
|
+
}
|
|
949
|
+
handleSupabaseError(error);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
716
952
|
/**
|
|
717
953
|
* Получить список экземпляров
|
|
718
954
|
* Поддерживает поиск, фильтры (JSONB и relation), пагинацию
|
|
@@ -1101,9 +1337,27 @@ var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
|
|
|
1101
1337
|
);
|
|
1102
1338
|
}
|
|
1103
1339
|
const { data: instanceData, relations } = data;
|
|
1340
|
+
const name = instanceData.name;
|
|
1341
|
+
if (!name || typeof name !== "string") {
|
|
1342
|
+
throw new Error(
|
|
1343
|
+
"Field 'name' is required and must be a string for slug generation"
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
const {
|
|
1347
|
+
generateUniqueSlugForInstance: generateUniqueSlugForInstance2
|
|
1348
|
+
} = await Promise.resolve().then(() => (init_slug(), slug_exports));
|
|
1349
|
+
const slug = await generateUniqueSlugForInstance2(
|
|
1350
|
+
name,
|
|
1351
|
+
entityDefinitionId,
|
|
1352
|
+
async (slugToCheck, entityDefIdToCheck) => {
|
|
1353
|
+
const { data: existing } = await this.supabase.from("entity_instance").select("id").eq("entity_definition_id", entityDefIdToCheck).eq("slug", slugToCheck).single();
|
|
1354
|
+
return !!existing;
|
|
1355
|
+
}
|
|
1356
|
+
);
|
|
1104
1357
|
const { data: instance, error: instanceError } = await this.supabase.from("entity_instance").insert({
|
|
1105
1358
|
entity_definition_id: entityDefinitionId,
|
|
1106
1359
|
project_id: this.projectId,
|
|
1360
|
+
slug,
|
|
1107
1361
|
data: instanceData,
|
|
1108
1362
|
created_by: (user == null ? void 0 : user.id) || null
|
|
1109
1363
|
}).select().single();
|