@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/dist/server.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { S as SDKOptions, P as PublicAPIClient } from './client-DqqjGYgA.mjs';
1
+ import { S as SDKOptions, P as PublicAPIClient } from './client-DS5xnLAo.mjs';
2
2
  import '@supabase/supabase-js';
3
3
 
4
4
  /**
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { S as SDKOptions, P as PublicAPIClient } from './client-DqqjGYgA.js';
1
+ import { S as SDKOptions, P as PublicAPIClient } from './client-DS5xnLAo.js';
2
2
  import '@supabase/supabase-js';
3
3
 
4
4
  /**
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();