@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.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { createServerClient as createServerClient$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
|
function createServerClient(supabaseUrl, supabaseAnonKey, options) {
|
|
23
79
|
return createServerClient$1(supabaseUrl, supabaseAnonKey, {
|
|
24
80
|
cookies: (options == null ? void 0 : options.cookies) || {
|
|
@@ -224,6 +280,7 @@ var BasePublicAPIClient = class {
|
|
|
224
280
|
return {
|
|
225
281
|
id: row.id,
|
|
226
282
|
name: row.name,
|
|
283
|
+
slug: row.slug,
|
|
227
284
|
description: row.description,
|
|
228
285
|
tableName: row.table_name,
|
|
229
286
|
type: row.type,
|
|
@@ -346,6 +403,38 @@ var BasePublicAPIClient = class {
|
|
|
346
403
|
}
|
|
347
404
|
return config;
|
|
348
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* Получить все EntityDefinitions проекта с полями одним запросом (JOIN)
|
|
408
|
+
* Используется для загрузки всех сущностей в layout
|
|
409
|
+
*
|
|
410
|
+
* @returns Массив EntityDefinitionConfig с полями
|
|
411
|
+
*/
|
|
412
|
+
async getAllEntityDefinitions() {
|
|
413
|
+
const { data, error } = await this.supabase.from("entity_definition").select(
|
|
414
|
+
`
|
|
415
|
+
*,
|
|
416
|
+
field!field_entity_definition_id_fkey (*)
|
|
417
|
+
`
|
|
418
|
+
).eq("project_id", this.projectId).order("name");
|
|
419
|
+
if (error) {
|
|
420
|
+
throw new Error(`Failed to load entity definitions: ${error.message}`);
|
|
421
|
+
}
|
|
422
|
+
if (!data || data.length === 0) {
|
|
423
|
+
return [];
|
|
424
|
+
}
|
|
425
|
+
return data.map((row) => {
|
|
426
|
+
const entityDefinition = this.transformEntityDefinitionFromDB(row);
|
|
427
|
+
const fields = (row.field || []).map(
|
|
428
|
+
(fieldRow) => this.transformFieldFromDB(fieldRow)
|
|
429
|
+
);
|
|
430
|
+
fields.sort(
|
|
431
|
+
(a, b) => a.displayIndex - b.displayIndex
|
|
432
|
+
);
|
|
433
|
+
return __spreadProps(__spreadValues({}, entityDefinition), {
|
|
434
|
+
fields
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
}
|
|
349
438
|
/**
|
|
350
439
|
* Преобразование EntityDefinitionConfig в EntityDefinition
|
|
351
440
|
*/
|
|
@@ -353,6 +442,7 @@ var BasePublicAPIClient = class {
|
|
|
353
442
|
return {
|
|
354
443
|
id: config.id,
|
|
355
444
|
name: config.name,
|
|
445
|
+
slug: config.slug,
|
|
356
446
|
description: config.description,
|
|
357
447
|
tableName: config.tableName,
|
|
358
448
|
type: config.type,
|
|
@@ -464,6 +554,7 @@ var BasePublicAPIClient = class {
|
|
|
464
554
|
function transformEntityInstance(row) {
|
|
465
555
|
return {
|
|
466
556
|
id: row.id,
|
|
557
|
+
slug: row.slug,
|
|
467
558
|
entityDefinitionId: row.entity_definition_id,
|
|
468
559
|
projectId: row.project_id,
|
|
469
560
|
data: row.data || {},
|
|
@@ -474,6 +565,7 @@ function transformEntityInstance(row) {
|
|
|
474
565
|
function flattenInstance(instance, fields, relationsAsIds = false) {
|
|
475
566
|
const result = {
|
|
476
567
|
id: instance.id,
|
|
568
|
+
slug: instance.slug,
|
|
477
569
|
entityDefinitionId: instance.entityDefinitionId,
|
|
478
570
|
projectId: instance.projectId,
|
|
479
571
|
createdAt: instance.createdAt,
|
|
@@ -501,6 +593,9 @@ function flattenInstance(instance, fields, relationsAsIds = false) {
|
|
|
501
593
|
return result;
|
|
502
594
|
}
|
|
503
595
|
|
|
596
|
+
// src/client.ts
|
|
597
|
+
init_slug();
|
|
598
|
+
|
|
504
599
|
// src/errors.ts
|
|
505
600
|
var SDKError = class extends Error {
|
|
506
601
|
constructor(code, message, statusCode, details) {
|
|
@@ -529,6 +624,19 @@ var PermissionDeniedError = class extends SDKError {
|
|
|
529
624
|
);
|
|
530
625
|
}
|
|
531
626
|
};
|
|
627
|
+
var ValidationError = class extends SDKError {
|
|
628
|
+
constructor(field, message) {
|
|
629
|
+
super(
|
|
630
|
+
"VALIDATION_ERROR",
|
|
631
|
+
`Validation failed for ${field}: ${message}`,
|
|
632
|
+
400,
|
|
633
|
+
{
|
|
634
|
+
field,
|
|
635
|
+
message
|
|
636
|
+
}
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
532
640
|
function handleSupabaseError(error) {
|
|
533
641
|
if (error.code === "PGRST116") {
|
|
534
642
|
throw new NotFoundError("Resource");
|
|
@@ -711,6 +819,134 @@ var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
|
|
|
711
819
|
handleSupabaseError(error);
|
|
712
820
|
}
|
|
713
821
|
}
|
|
822
|
+
/**
|
|
823
|
+
* Получить один экземпляр по slug
|
|
824
|
+
*/
|
|
825
|
+
async getInstanceBySlug(entityDefinitionId, slug, params) {
|
|
826
|
+
var _a;
|
|
827
|
+
try {
|
|
828
|
+
if (!validateSlug(slug)) {
|
|
829
|
+
throw new ValidationError(
|
|
830
|
+
"slug",
|
|
831
|
+
"Slug must contain only lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen"
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
const fields = await this.getFields(entityDefinitionId);
|
|
835
|
+
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();
|
|
836
|
+
if (instanceError || !instance) {
|
|
837
|
+
handleInstanceError(
|
|
838
|
+
instanceError || new Error("Instance not found"),
|
|
839
|
+
slug
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
const transformedInstance = transformEntityInstance(instance);
|
|
843
|
+
if (transformedInstance.entityDefinitionId !== entityDefinitionId) {
|
|
844
|
+
throw new NotFoundError("Entity instance", slug);
|
|
845
|
+
}
|
|
846
|
+
const relationFields = fields.filter(
|
|
847
|
+
(f) => f.dbType === "manyToMany" || f.dbType === "manyToOne" || f.dbType === "oneToMany" || f.dbType === "oneToOne"
|
|
848
|
+
);
|
|
849
|
+
const relations = {};
|
|
850
|
+
if (relationFields.length > 0) {
|
|
851
|
+
const relationFieldIds = relationFields.map((f) => f.id).filter((id) => Boolean(id));
|
|
852
|
+
if (relationFieldIds.length > 0) {
|
|
853
|
+
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);
|
|
854
|
+
if (relationsError) {
|
|
855
|
+
throw new SDKError(
|
|
856
|
+
"RELATIONS_LOAD_ERROR",
|
|
857
|
+
`Failed to load relations for instance with slug ${slug}: ${relationsError.message}`,
|
|
858
|
+
500,
|
|
859
|
+
relationsError
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
if (allRelations && allRelations.length > 0) {
|
|
863
|
+
const targetInstanceIds = [
|
|
864
|
+
...new Set(allRelations.map((r) => r.target_instance_id))
|
|
865
|
+
];
|
|
866
|
+
const { data: relatedInstances, error: instancesError } = await this.supabase.from("entity_instance").select("*").in("id", targetInstanceIds);
|
|
867
|
+
if (instancesError) {
|
|
868
|
+
throw new SDKError(
|
|
869
|
+
"RELATED_INSTANCES_LOAD_ERROR",
|
|
870
|
+
`Failed to load related instances for instance with slug ${slug}: ${instancesError.message}`,
|
|
871
|
+
500,
|
|
872
|
+
instancesError
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
if (relatedInstances) {
|
|
876
|
+
const relatedInstancesMap = new Map(
|
|
877
|
+
relatedInstances.map((inst) => [
|
|
878
|
+
inst.id,
|
|
879
|
+
transformEntityInstance(inst)
|
|
880
|
+
])
|
|
881
|
+
);
|
|
882
|
+
for (const relation of allRelations) {
|
|
883
|
+
const relationField = relationFields.find(
|
|
884
|
+
(f) => f.id === relation.relation_field_id
|
|
885
|
+
);
|
|
886
|
+
if (relationField) {
|
|
887
|
+
const relatedInstance = relatedInstancesMap.get(
|
|
888
|
+
relation.target_instance_id
|
|
889
|
+
);
|
|
890
|
+
if (relatedInstance) {
|
|
891
|
+
if (!relations[relationField.name]) {
|
|
892
|
+
relations[relationField.name] = [];
|
|
893
|
+
}
|
|
894
|
+
relations[relationField.name].push(
|
|
895
|
+
relatedInstance
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
const fileFields = fields.filter(
|
|
905
|
+
(f) => f.type === "files" || f.type === "images"
|
|
906
|
+
);
|
|
907
|
+
if (fileFields.length > 0) {
|
|
908
|
+
const { data: allFiles, error: filesError } = await this.supabase.from("entity_file").select("id, field_id").eq("entity_instance_id", transformedInstance.id);
|
|
909
|
+
if (filesError) {
|
|
910
|
+
throw new SDKError(
|
|
911
|
+
"FILES_LOAD_ERROR",
|
|
912
|
+
`Failed to load files for instance with slug ${slug}: ${filesError.message}`,
|
|
913
|
+
500,
|
|
914
|
+
filesError
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
if (allFiles) {
|
|
918
|
+
const filesByFieldId = /* @__PURE__ */ new Map();
|
|
919
|
+
allFiles.forEach((file) => {
|
|
920
|
+
if (file.field_id) {
|
|
921
|
+
if (!filesByFieldId.has(file.field_id)) {
|
|
922
|
+
filesByFieldId.set(file.field_id, []);
|
|
923
|
+
}
|
|
924
|
+
filesByFieldId.get(file.field_id).push(file.id);
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
fileFields.forEach((field) => {
|
|
928
|
+
const fileIds = filesByFieldId.get(field.id) || [];
|
|
929
|
+
if (fileIds.length > 0 || !transformedInstance.data[field.name]) {
|
|
930
|
+
transformedInstance.data[field.name] = fileIds;
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
const instanceWithRelations = __spreadProps(__spreadValues({}, transformedInstance), {
|
|
936
|
+
relations: Object.keys(relations).length > 0 ? relations : void 0
|
|
937
|
+
});
|
|
938
|
+
return flattenInstance(
|
|
939
|
+
instanceWithRelations,
|
|
940
|
+
fields.map((f) => ({ name: f.name, dbType: f.dbType })),
|
|
941
|
+
(_a = params == null ? void 0 : params.relationsAsIds) != null ? _a : false
|
|
942
|
+
);
|
|
943
|
+
} catch (error) {
|
|
944
|
+
if (error instanceof NotFoundError || error instanceof PermissionDeniedError || error instanceof ValidationError || error instanceof SDKError) {
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
947
|
+
handleSupabaseError(error);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
714
950
|
/**
|
|
715
951
|
* Получить список экземпляров
|
|
716
952
|
* Поддерживает поиск, фильтры (JSONB и relation), пагинацию
|
|
@@ -1099,9 +1335,27 @@ var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
|
|
|
1099
1335
|
);
|
|
1100
1336
|
}
|
|
1101
1337
|
const { data: instanceData, relations } = data;
|
|
1338
|
+
const name = instanceData.name;
|
|
1339
|
+
if (!name || typeof name !== "string") {
|
|
1340
|
+
throw new Error(
|
|
1341
|
+
"Field 'name' is required and must be a string for slug generation"
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
const {
|
|
1345
|
+
generateUniqueSlugForInstance: generateUniqueSlugForInstance2
|
|
1346
|
+
} = await Promise.resolve().then(() => (init_slug(), slug_exports));
|
|
1347
|
+
const slug = await generateUniqueSlugForInstance2(
|
|
1348
|
+
name,
|
|
1349
|
+
entityDefinitionId,
|
|
1350
|
+
async (slugToCheck, entityDefIdToCheck) => {
|
|
1351
|
+
const { data: existing } = await this.supabase.from("entity_instance").select("id").eq("entity_definition_id", entityDefIdToCheck).eq("slug", slugToCheck).single();
|
|
1352
|
+
return !!existing;
|
|
1353
|
+
}
|
|
1354
|
+
);
|
|
1102
1355
|
const { data: instance, error: instanceError } = await this.supabase.from("entity_instance").insert({
|
|
1103
1356
|
entity_definition_id: entityDefinitionId,
|
|
1104
1357
|
project_id: this.projectId,
|
|
1358
|
+
slug,
|
|
1105
1359
|
data: instanceData,
|
|
1106
1360
|
created_by: (user == null ? void 0 : user.id) || null
|
|
1107
1361
|
}).select().single();
|