@igorchugurov/public-api-sdk 1.1.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 CHANGED
@@ -152,7 +152,7 @@ const { data, pagination } = await sdk.getInstances(entityDefinitionId, {
152
152
 
153
153
  #### `getInstance(entityDefinitionId, id, params?)`
154
154
 
155
- Получить один экземпляр.
155
+ Получить один экземпляр по ID.
156
156
 
157
157
  ```typescript
158
158
  const instance = await sdk.getInstance(entityDefinitionId, id, {
@@ -160,6 +160,40 @@ const instance = await sdk.getInstance(entityDefinitionId, id, {
160
160
  });
161
161
  ```
162
162
 
163
+ **Пример:**
164
+
165
+ ```typescript
166
+ const instance = await sdk.getInstance("entity-def-id", "instance-id", {
167
+ relationsAsIds: true, // для редактирования - нужны только ID
168
+ });
169
+ ```
170
+
171
+ #### `getInstanceBySlug(entityDefinitionId, slug, params?)`
172
+
173
+ Получить один экземпляр по slug.
174
+
175
+ ```typescript
176
+ const instance = await sdk.getInstanceBySlug(entityDefinitionId, slug, {
177
+ relationsAsIds?: boolean; // default: false
178
+ });
179
+ ```
180
+
181
+ **Пример:**
182
+
183
+ ```typescript
184
+ const instance = await sdk.getInstanceBySlug("entity-def-id", "my-article-slug", {
185
+ relationsAsIds: false, // для отображения - нужны полные объекты
186
+ });
187
+ ```
188
+
189
+ **Особенности:**
190
+ - Валидирует формат slug перед запросом (только строчные латинские буквы, цифры и дефисы)
191
+ - Работает аналогично `getInstance`, но ищет по slug вместо id
192
+ - Поддерживает те же параметры, что и `getInstance` (relationsAsIds)
193
+ - Параметр `relationsAsIds`:
194
+ - `false` (по умолчанию) - возвращает полные объекты связанных сущностей
195
+ - `true` - возвращает только массивы ID связанных сущностей
196
+
163
197
  #### `createInstance(entityDefinitionId, data)`
164
198
 
165
199
  Создать новый экземпляр. Автоматически генерирует уникальный `slug` из поля `name`.
@@ -1109,6 +1109,9 @@ declare abstract class BasePublicAPIClient {
1109
1109
  abstract getInstance(entityDefinitionId: string, id: string, params?: {
1110
1110
  relationsAsIds?: boolean;
1111
1111
  }): Promise<EntityInstanceWithFields>;
1112
+ abstract getInstanceBySlug(entityDefinitionId: string, slug: string, params?: {
1113
+ relationsAsIds?: boolean;
1114
+ }): Promise<EntityInstanceWithFields>;
1112
1115
  abstract createInstance(entityDefinitionId: string, data: CreateInstanceData): Promise<EntityInstanceWithFields>;
1113
1116
  abstract updateInstance(entityDefinitionId: string, id: string, data: UpdateInstanceData): Promise<EntityInstanceWithFields>;
1114
1117
  abstract deleteInstance(entityDefinitionId: string, id: string): Promise<void>;
@@ -1152,6 +1155,12 @@ declare class PublicAPIClient extends BasePublicAPIClient {
1152
1155
  getInstance(entityDefinitionId: string, id: string, params?: {
1153
1156
  relationsAsIds?: boolean;
1154
1157
  }): Promise<EntityInstanceWithFields>;
1158
+ /**
1159
+ * Получить один экземпляр по slug
1160
+ */
1161
+ getInstanceBySlug(entityDefinitionId: string, slug: string, params?: {
1162
+ relationsAsIds?: boolean;
1163
+ }): Promise<EntityInstanceWithFields>;
1155
1164
  /**
1156
1165
  * Получить список экземпляров
1157
1166
  * Поддерживает поиск, фильтры (JSONB и relation), пагинацию
@@ -1109,6 +1109,9 @@ declare abstract class BasePublicAPIClient {
1109
1109
  abstract getInstance(entityDefinitionId: string, id: string, params?: {
1110
1110
  relationsAsIds?: boolean;
1111
1111
  }): Promise<EntityInstanceWithFields>;
1112
+ abstract getInstanceBySlug(entityDefinitionId: string, slug: string, params?: {
1113
+ relationsAsIds?: boolean;
1114
+ }): Promise<EntityInstanceWithFields>;
1112
1115
  abstract createInstance(entityDefinitionId: string, data: CreateInstanceData): Promise<EntityInstanceWithFields>;
1113
1116
  abstract updateInstance(entityDefinitionId: string, id: string, data: UpdateInstanceData): Promise<EntityInstanceWithFields>;
1114
1117
  abstract deleteInstance(entityDefinitionId: string, id: string): Promise<void>;
@@ -1152,6 +1155,12 @@ declare class PublicAPIClient extends BasePublicAPIClient {
1152
1155
  getInstance(entityDefinitionId: string, id: string, params?: {
1153
1156
  relationsAsIds?: boolean;
1154
1157
  }): Promise<EntityInstanceWithFields>;
1158
+ /**
1159
+ * Получить один экземпляр по slug
1160
+ */
1161
+ getInstanceBySlug(entityDefinitionId: string, slug: string, params?: {
1162
+ relationsAsIds?: boolean;
1163
+ }): Promise<EntityInstanceWithFields>;
1155
1164
  /**
1156
1165
  * Получить список экземпляров
1157
1166
  * Поддерживает поиск, фильтры (JSONB и relation), пагинацию
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FieldConfig, a as FieldValue, E as EntityDefinition, b as Field, c as EntityUIConfig, C as ColumnConfig, S as SDKOptions, P as PublicAPIClient } from './client-BV5AAKYo.mjs';
2
- export { y as ActionConfig, A as AuthResult, g as CreateInstanceData, D as DbType, l as DbTypeToTSType, m as EntityData, e as EntityDefinitionConfig, x as EntityFile, n as EntityInstance, q as EntityInstanceWithFields, p as EntityRelation, k as FieldOption, j as FieldType, r as FilterValue, z as FormPageConfig, G as GetInstancesOptions, I as InstanceData, L as ListPageConfig, M as MessagesConfig, h as PaginationResult, w as PartialInstanceData, B as PartialUIConfig, d as ProjectConfig, Q as QueryParams, R as RelationFilterInfo, f as RelationFilterMode, o as RelationType, s as RelationsData, i as SignUpData, U as UpdateInstanceData, v as getFieldValue, u as isEntityData, t as isFieldValue } from './client-BV5AAKYo.mjs';
1
+ import { F as FieldConfig, a as FieldValue, E as EntityDefinition, b as Field, c as EntityUIConfig, C as ColumnConfig, S as SDKOptions, P as PublicAPIClient } from './client-DS5xnLAo.mjs';
2
+ export { y as ActionConfig, A as AuthResult, g as CreateInstanceData, D as DbType, l as DbTypeToTSType, m as EntityData, e as EntityDefinitionConfig, x as EntityFile, n as EntityInstance, q as EntityInstanceWithFields, p as EntityRelation, k as FieldOption, j as FieldType, r as FilterValue, z as FormPageConfig, G as GetInstancesOptions, I as InstanceData, L as ListPageConfig, M as MessagesConfig, h as PaginationResult, w as PartialInstanceData, B as PartialUIConfig, d as ProjectConfig, Q as QueryParams, R as RelationFilterInfo, f as RelationFilterMode, o as RelationType, s as RelationsData, i as SignUpData, U as UpdateInstanceData, v as getFieldValue, u as isEntityData, t as isFieldValue } from './client-DS5xnLAo.mjs';
3
3
  import '@supabase/supabase-js';
4
4
 
5
5
  /**
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FieldConfig, a as FieldValue, E as EntityDefinition, b as Field, c as EntityUIConfig, C as ColumnConfig, S as SDKOptions, P as PublicAPIClient } from './client-BV5AAKYo.js';
2
- export { y as ActionConfig, A as AuthResult, g as CreateInstanceData, D as DbType, l as DbTypeToTSType, m as EntityData, e as EntityDefinitionConfig, x as EntityFile, n as EntityInstance, q as EntityInstanceWithFields, p as EntityRelation, k as FieldOption, j as FieldType, r as FilterValue, z as FormPageConfig, G as GetInstancesOptions, I as InstanceData, L as ListPageConfig, M as MessagesConfig, h as PaginationResult, w as PartialInstanceData, B as PartialUIConfig, d as ProjectConfig, Q as QueryParams, R as RelationFilterInfo, f as RelationFilterMode, o as RelationType, s as RelationsData, i as SignUpData, U as UpdateInstanceData, v as getFieldValue, u as isEntityData, t as isFieldValue } from './client-BV5AAKYo.js';
1
+ import { F as FieldConfig, a as FieldValue, E as EntityDefinition, b as Field, c as EntityUIConfig, C as ColumnConfig, S as SDKOptions, P as PublicAPIClient } from './client-DS5xnLAo.js';
2
+ export { y as ActionConfig, A as AuthResult, g as CreateInstanceData, D as DbType, l as DbTypeToTSType, m as EntityData, e as EntityDefinitionConfig, x as EntityFile, n as EntityInstance, q as EntityInstanceWithFields, p as EntityRelation, k as FieldOption, j as FieldType, r as FilterValue, z as FormPageConfig, G as GetInstancesOptions, I as InstanceData, L as ListPageConfig, M as MessagesConfig, h as PaginationResult, w as PartialInstanceData, B as PartialUIConfig, d as ProjectConfig, Q as QueryParams, R as RelationFilterInfo, f as RelationFilterMode, o as RelationType, s as RelationsData, i as SignUpData, U as UpdateInstanceData, v as getFieldValue, u as isEntityData, t as isFieldValue } from './client-DS5xnLAo.js';
3
3
  import '@supabase/supabase-js';
4
4
 
5
5
  /**
package/dist/index.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
  }
@@ -756,6 +767,7 @@ function flattenInstance(instance, fields, relationsAsIds = false) {
756
767
  }
757
768
 
758
769
  // src/client.ts
770
+ init_slug();
759
771
  var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
760
772
  constructor(supabase, projectId, mode, options = {}) {
761
773
  super(supabase, projectId, options);
@@ -907,6 +919,134 @@ var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
907
919
  handleSupabaseError(error);
908
920
  }
909
921
  }
922
+ /**
923
+ * Получить один экземпляр по slug
924
+ */
925
+ async getInstanceBySlug(entityDefinitionId, slug, params) {
926
+ var _a;
927
+ try {
928
+ if (!validateSlug(slug)) {
929
+ throw new ValidationError(
930
+ "slug",
931
+ "Slug must contain only lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen"
932
+ );
933
+ }
934
+ const fields = await this.getFields(entityDefinitionId);
935
+ 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();
936
+ if (instanceError || !instance) {
937
+ handleInstanceError(
938
+ instanceError || new Error("Instance not found"),
939
+ slug
940
+ );
941
+ }
942
+ const transformedInstance = transformEntityInstance(instance);
943
+ if (transformedInstance.entityDefinitionId !== entityDefinitionId) {
944
+ throw new NotFoundError("Entity instance", slug);
945
+ }
946
+ const relationFields = fields.filter(
947
+ (f) => f.dbType === "manyToMany" || f.dbType === "manyToOne" || f.dbType === "oneToMany" || f.dbType === "oneToOne"
948
+ );
949
+ const relations = {};
950
+ if (relationFields.length > 0) {
951
+ const relationFieldIds = relationFields.map((f) => f.id).filter((id) => Boolean(id));
952
+ if (relationFieldIds.length > 0) {
953
+ 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);
954
+ if (relationsError) {
955
+ throw new SDKError(
956
+ "RELATIONS_LOAD_ERROR",
957
+ `Failed to load relations for instance with slug ${slug}: ${relationsError.message}`,
958
+ 500,
959
+ relationsError
960
+ );
961
+ }
962
+ if (allRelations && allRelations.length > 0) {
963
+ const targetInstanceIds = [
964
+ ...new Set(allRelations.map((r) => r.target_instance_id))
965
+ ];
966
+ const { data: relatedInstances, error: instancesError } = await this.supabase.from("entity_instance").select("*").in("id", targetInstanceIds);
967
+ if (instancesError) {
968
+ throw new SDKError(
969
+ "RELATED_INSTANCES_LOAD_ERROR",
970
+ `Failed to load related instances for instance with slug ${slug}: ${instancesError.message}`,
971
+ 500,
972
+ instancesError
973
+ );
974
+ }
975
+ if (relatedInstances) {
976
+ const relatedInstancesMap = new Map(
977
+ relatedInstances.map((inst) => [
978
+ inst.id,
979
+ transformEntityInstance(inst)
980
+ ])
981
+ );
982
+ for (const relation of allRelations) {
983
+ const relationField = relationFields.find(
984
+ (f) => f.id === relation.relation_field_id
985
+ );
986
+ if (relationField) {
987
+ const relatedInstance = relatedInstancesMap.get(
988
+ relation.target_instance_id
989
+ );
990
+ if (relatedInstance) {
991
+ if (!relations[relationField.name]) {
992
+ relations[relationField.name] = [];
993
+ }
994
+ relations[relationField.name].push(
995
+ relatedInstance
996
+ );
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+ }
1003
+ }
1004
+ const fileFields = fields.filter(
1005
+ (f) => f.type === "files" || f.type === "images"
1006
+ );
1007
+ if (fileFields.length > 0) {
1008
+ const { data: allFiles, error: filesError } = await this.supabase.from("entity_file").select("id, field_id").eq("entity_instance_id", transformedInstance.id);
1009
+ if (filesError) {
1010
+ throw new SDKError(
1011
+ "FILES_LOAD_ERROR",
1012
+ `Failed to load files for instance with slug ${slug}: ${filesError.message}`,
1013
+ 500,
1014
+ filesError
1015
+ );
1016
+ }
1017
+ if (allFiles) {
1018
+ const filesByFieldId = /* @__PURE__ */ new Map();
1019
+ allFiles.forEach((file) => {
1020
+ if (file.field_id) {
1021
+ if (!filesByFieldId.has(file.field_id)) {
1022
+ filesByFieldId.set(file.field_id, []);
1023
+ }
1024
+ filesByFieldId.get(file.field_id).push(file.id);
1025
+ }
1026
+ });
1027
+ fileFields.forEach((field) => {
1028
+ const fileIds = filesByFieldId.get(field.id) || [];
1029
+ if (fileIds.length > 0 || !transformedInstance.data[field.name]) {
1030
+ transformedInstance.data[field.name] = fileIds;
1031
+ }
1032
+ });
1033
+ }
1034
+ }
1035
+ const instanceWithRelations = __spreadProps(__spreadValues({}, transformedInstance), {
1036
+ relations: Object.keys(relations).length > 0 ? relations : void 0
1037
+ });
1038
+ return flattenInstance(
1039
+ instanceWithRelations,
1040
+ fields.map((f) => ({ name: f.name, dbType: f.dbType })),
1041
+ (_a = params == null ? void 0 : params.relationsAsIds) != null ? _a : false
1042
+ );
1043
+ } catch (error) {
1044
+ if (error instanceof NotFoundError || error instanceof PermissionDeniedError || error instanceof ValidationError || error instanceof SDKError) {
1045
+ throw error;
1046
+ }
1047
+ handleSupabaseError(error);
1048
+ }
1049
+ }
910
1050
  /**
911
1051
  * Получить список экземпляров
912
1052
  * Поддерживает поиск, фильтры (JSONB и relation), пагинацию