@igorchugurov/public-api-sdk 1.0.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.
@@ -0,0 +1,1390 @@
1
+ import { createServerClient as createServerClient$1 } from '@supabase/ssr';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
6
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
9
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
+ var __spreadValues = (a, b) => {
11
+ for (var prop in b || (b = {}))
12
+ if (__hasOwnProp.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ if (__getOwnPropSymbols)
15
+ for (var prop of __getOwnPropSymbols(b)) {
16
+ if (__propIsEnum.call(b, prop))
17
+ __defNormalProp(a, prop, b[prop]);
18
+ }
19
+ return a;
20
+ };
21
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
22
+ function createServerClient(supabaseUrl, supabaseAnonKey, options) {
23
+ return createServerClient$1(supabaseUrl, supabaseAnonKey, {
24
+ cookies: (options == null ? void 0 : options.cookies) || {
25
+ getAll: () => [],
26
+ setAll: () => {
27
+ }
28
+ },
29
+ global: {
30
+ fetch: (url, options2) => fetch(url, __spreadProps(__spreadValues({}, options2), { cache: "no-store" }))
31
+ }
32
+ });
33
+ }
34
+
35
+ // src/utils/generateUIConfig.ts
36
+ function generateUIConfig(entityDefinition, fields) {
37
+ const defaults = generateDefaults(entityDefinition, fields);
38
+ if (entityDefinition.uiConfig) {
39
+ return deepMerge(
40
+ defaults,
41
+ entityDefinition.uiConfig
42
+ );
43
+ }
44
+ return defaults;
45
+ }
46
+ function generateDefaults(entityDefinition, fields) {
47
+ var _a, _b, _c;
48
+ const name = entityDefinition.name;
49
+ const nameLower = name.toLowerCase();
50
+ const namePlural = pluralize(nameLower);
51
+ const tableName = entityDefinition.tableName;
52
+ const searchableFields = fields.filter((f) => f.searchable).map((f) => f.name);
53
+ const finalSearchableFields = searchableFields.length > 0 ? searchableFields : ["name"];
54
+ const list = {
55
+ pageTitle: name,
56
+ searchPlaceholder: `Search for ${namePlural}...`,
57
+ emptyStateTitle: `You have no ${namePlural}`,
58
+ emptyStateMessages: [
59
+ `${name}s that you create will end up here.`,
60
+ `Add a ${nameLower} to get started.`
61
+ ],
62
+ showCreateButton: true,
63
+ createButtonText: `New ${nameLower}`,
64
+ showSearch: true,
65
+ enablePagination: (_a = entityDefinition.enablePagination) != null ? _a : true,
66
+ pageSize: (_b = entityDefinition.pageSize) != null ? _b : 20,
67
+ enableFilters: (_c = entityDefinition.enableFilters) != null ? _c : false,
68
+ searchableFields: finalSearchableFields,
69
+ columns: generateColumns(fields)
70
+ };
71
+ const form = {
72
+ createPageTitle: `Create new ${nameLower}`,
73
+ editPageTitle: `Edit ${nameLower}`,
74
+ pageHeader: `${name} details`,
75
+ createButtonLabel: "Create",
76
+ updateButtonLabel: "Update",
77
+ cancelButtonLabel: "Cancel",
78
+ sectionTitles: {
79
+ 0: entityDefinition.titleSection0 || "General Information",
80
+ 1: entityDefinition.titleSection1 || "Section 1",
81
+ 2: entityDefinition.titleSection2 || "Section 2",
82
+ 3: entityDefinition.titleSection3 || "Section 3"
83
+ }
84
+ };
85
+ const messages = {
86
+ afterCreate: `${name} was created successfully!`,
87
+ afterUpdate: `${name} was updated successfully!`,
88
+ afterDelete: `${name} was deleted successfully!`,
89
+ errorCreate: `Failed to create ${nameLower}. Please try again.`,
90
+ errorUpdate: `Failed to update ${nameLower}. Please try again.`,
91
+ deleteModalTitle: `Confirm deleting ${nameLower}`,
92
+ deleteModalText: `Are you sure you want to delete "{itemName}"? This action cannot be undone.`,
93
+ deleteModalButtonText: "Delete",
94
+ reloadEvents: {
95
+ create: `reload${name}`,
96
+ update: `reload${name}`,
97
+ delete: `reload${name}`
98
+ }
99
+ };
100
+ return {
101
+ apiUrl: `/api/${tableName}`,
102
+ apiUrlAll: `/api/entity-instances/all`,
103
+ list,
104
+ form,
105
+ messages
106
+ };
107
+ }
108
+ function isRelationField(field) {
109
+ return field.dbType === "manyToOne" || field.dbType === "oneToOne" || field.dbType === "manyToMany" || field.dbType === "oneToMany";
110
+ }
111
+ function generateColumns(fields) {
112
+ const displayFields = fields.filter((f) => f.displayInTable).sort((a, b) => a.displayIndex - b.displayIndex);
113
+ const columns = displayFields.map((field, index) => {
114
+ const columnType = index === 0 ? "naigateToDetails" : getColumnType(field);
115
+ const isRelation = isRelationField(field);
116
+ const column = __spreadValues({
117
+ field: field.name,
118
+ headerName: field.label || field.name,
119
+ flex: 1,
120
+ type: columnType,
121
+ sortable: true
122
+ }, isRelation && {
123
+ relationDbType: field.dbType
124
+ });
125
+ return column;
126
+ });
127
+ columns.push({
128
+ field: "actions",
129
+ headerName: "",
130
+ type: "actions",
131
+ width: 100,
132
+ actions: [
133
+ {
134
+ action: "edit",
135
+ link: true
136
+ },
137
+ {
138
+ action: "delete"
139
+ }
140
+ ]
141
+ });
142
+ return columns;
143
+ }
144
+ function getColumnType(field) {
145
+ if (isRelationField(field)) {
146
+ return "relation";
147
+ }
148
+ switch (field.type) {
149
+ case "date":
150
+ return "date";
151
+ case "number":
152
+ return "number";
153
+ case "boolean":
154
+ return "boolean";
155
+ default:
156
+ return "text";
157
+ }
158
+ }
159
+ function pluralize(word) {
160
+ if (word.endsWith("y")) {
161
+ return word.slice(0, -1) + "ies";
162
+ }
163
+ if (word.endsWith("s") || word.endsWith("x") || word.endsWith("ch") || word.endsWith("sh")) {
164
+ return word + "es";
165
+ }
166
+ return word + "s";
167
+ }
168
+ function deepMerge(defaults, custom) {
169
+ const result = __spreadValues({}, defaults);
170
+ for (const key in custom) {
171
+ const customValue = custom[key];
172
+ const defaultValue = defaults[key];
173
+ if (customValue === void 0 || customValue === null) {
174
+ continue;
175
+ }
176
+ if (typeof customValue === "object" && !Array.isArray(customValue) && typeof defaultValue === "object" && !Array.isArray(defaultValue) && defaultValue !== null) {
177
+ result[key] = deepMerge(
178
+ defaultValue,
179
+ customValue
180
+ );
181
+ } else {
182
+ result[key] = customValue;
183
+ }
184
+ }
185
+ return result;
186
+ }
187
+
188
+ // src/base/base-client.ts
189
+ var BasePublicAPIClient = class {
190
+ constructor(supabase, projectId, options = {}) {
191
+ this.configCache = /* @__PURE__ */ new Map();
192
+ var _a, _b;
193
+ this.supabase = supabase;
194
+ this.projectId = projectId;
195
+ this.enableCache = (_a = options.enableCache) != null ? _a : true;
196
+ this.cacheTTL = (_b = options.cacheTTL) != null ? _b : 5 * 60 * 1e3;
197
+ }
198
+ /**
199
+ * Загрузить fields для entityDefinition (с кэшированием)
200
+ *
201
+ * Флоу работы с кэшем:
202
+ *
203
+ * 1. Если enableCache === true (публичный API):
204
+ * - Проверяет кэш по entityDefinitionId
205
+ * - Если есть в кэше и не истек → возвращает из кэша (0 запросов к БД)
206
+ * - Если нет в кэше или истек → загружает из БД и кэширует (1 запрос к БД)
207
+ *
208
+ * 2. Если enableCache === false (админка):
209
+ * - Пропускает проверку кэша (эквивалент forceRefresh: true)
210
+ * - Всегда загружает из БД (1 запрос к БД)
211
+ * - Не сохраняет в кэш (всегда свежие данные)
212
+ *
213
+ * Используется внутри SDK для получения fields перед операциями с instances.
214
+ * Fields нужны для определения типов полей, relations, и уплощения данных.
215
+ */
216
+ async getFields(entityDefinitionId) {
217
+ const config = await this.getEntityDefinitionConfig(entityDefinitionId);
218
+ return config.fields;
219
+ }
220
+ /**
221
+ * Преобразование данных entity_definition из БД в EntityDefinitionConfig
222
+ */
223
+ transformEntityDefinitionFromDB(row) {
224
+ return {
225
+ id: row.id,
226
+ name: row.name,
227
+ description: row.description,
228
+ tableName: row.table_name,
229
+ type: row.type,
230
+ projectId: row.project_id,
231
+ createPermission: row.create_permission,
232
+ readPermission: row.read_permission,
233
+ updatePermission: row.update_permission,
234
+ deletePermission: row.delete_permission,
235
+ titleSection0: row.title_section_0,
236
+ titleSection1: row.title_section_1,
237
+ titleSection2: row.title_section_2,
238
+ titleSection3: row.title_section_3,
239
+ uiConfig: row.ui_config,
240
+ enablePagination: row.enable_pagination,
241
+ pageSize: row.page_size,
242
+ enableFilters: row.enable_filters,
243
+ maxFileSizeMb: row.max_file_size_mb,
244
+ maxFilesCount: row.max_files_count,
245
+ createdAt: row.created_at,
246
+ updatedAt: row.updated_at
247
+ };
248
+ }
249
+ /**
250
+ * Преобразование данных field из БД в FieldConfig
251
+ */
252
+ transformFieldFromDB(row) {
253
+ var _a;
254
+ return {
255
+ id: row.id,
256
+ entityDefinitionId: row.entity_definition_id,
257
+ name: row.name,
258
+ dbType: row.db_type,
259
+ type: row.type,
260
+ label: row.label,
261
+ placeholder: row.placeholder,
262
+ description: row.description,
263
+ forEditPage: row.for_edit_page,
264
+ forCreatePage: row.for_create_page,
265
+ required: row.required,
266
+ requiredText: row.required_text,
267
+ forEditPageDisabled: row.for_edit_page_disabled,
268
+ displayIndex: row.display_index,
269
+ displayInTable: row.display_in_table,
270
+ sectionIndex: (_a = row.section_index) != null ? _a : 0,
271
+ isOptionTitleField: row.is_option_title_field,
272
+ searchable: row.searchable,
273
+ filterableInList: row.filterable_in_list,
274
+ options: row.options,
275
+ relatedEntityDefinitionId: row.related_entity_definition_id,
276
+ relationFieldId: row.relation_field_id,
277
+ isRelationSource: row.is_relation_source,
278
+ selectorRelationId: row.selector_relation_id,
279
+ relationFieldName: row.relation_field_name,
280
+ relationFieldLabel: row.relation_field_label,
281
+ defaultStringValue: row.default_string_value,
282
+ defaultNumberValue: row.default_number_value,
283
+ defaultBooleanValue: row.default_boolean_value,
284
+ defaultDateValue: row.default_date_value,
285
+ autoPopulate: row.auto_populate,
286
+ includeInSinglePma: row.include_in_single_pma,
287
+ includeInListPma: row.include_in_list_pma,
288
+ includeInSingleSa: row.include_in_single_sa,
289
+ includeInListSa: row.include_in_list_sa,
290
+ foreignKey: row.foreign_key,
291
+ foreignKeyValue: row.foreign_key_value,
292
+ typeFieldName: row.type_field_name,
293
+ optionsFieldName: row.options_field_name,
294
+ acceptFileTypes: row.accept_file_types,
295
+ maxFileSize: row.max_file_size,
296
+ maxFiles: row.max_files,
297
+ storageBucket: row.storage_bucket,
298
+ createdAt: row.created_at,
299
+ updatedAt: row.updated_at
300
+ };
301
+ }
302
+ /**
303
+ * Загрузить entityDefinition с полями одним запросом (JOIN)
304
+ * Кэширует всю конфигурацию целиком (entityDefinition + fields)
305
+ *
306
+ * Логика кэширования:
307
+ * - enableCache: true → использует кэш, сохраняет в кэш (TTL: 5 минут по умолчанию)
308
+ * - enableCache: false → всегда загружает из БД, не использует кэш (эквивалент forceRefresh: true)
309
+ *
310
+ * @returns EntityDefinitionConfig с полями, отсортированными по display_index
311
+ */
312
+ async getEntityDefinitionConfig(entityDefinitionId) {
313
+ const forceRefresh = !this.enableCache;
314
+ if (!forceRefresh && this.enableCache) {
315
+ const cached = this.configCache.get(entityDefinitionId);
316
+ if (cached && Date.now() < cached.expiresAt) {
317
+ return cached.config;
318
+ }
319
+ }
320
+ const { data, error } = await this.supabase.from("entity_definition").select(
321
+ `
322
+ *,
323
+ field!field_entity_definition_id_fkey (*)
324
+ `
325
+ ).eq("id", entityDefinitionId).single();
326
+ if (error || !data) {
327
+ throw new Error(
328
+ `Entity definition not found: ${(error == null ? void 0 : error.message) || "Unknown error"}`
329
+ );
330
+ }
331
+ const entityDefinitionData = this.transformEntityDefinitionFromDB(data);
332
+ const fields = (data.field || []).sort((a, b) => {
333
+ var _a, _b;
334
+ const aIndex = (_a = a.display_index) != null ? _a : 999;
335
+ const bIndex = (_b = b.display_index) != null ? _b : 999;
336
+ return aIndex - bIndex;
337
+ }).map((row) => this.transformFieldFromDB(row));
338
+ const config = __spreadProps(__spreadValues({}, entityDefinitionData), {
339
+ fields
340
+ });
341
+ if (this.enableCache) {
342
+ this.configCache.set(entityDefinitionId, {
343
+ config,
344
+ expiresAt: Date.now() + this.cacheTTL
345
+ });
346
+ }
347
+ return config;
348
+ }
349
+ /**
350
+ * Преобразование EntityDefinitionConfig в EntityDefinition
351
+ */
352
+ convertToEntityDefinition(config) {
353
+ return {
354
+ id: config.id,
355
+ name: config.name,
356
+ description: config.description,
357
+ tableName: config.tableName,
358
+ type: config.type,
359
+ projectId: config.projectId,
360
+ createPermission: config.createPermission,
361
+ readPermission: config.readPermission,
362
+ updatePermission: config.updatePermission,
363
+ deletePermission: config.deletePermission,
364
+ titleSection0: config.titleSection0,
365
+ titleSection1: config.titleSection1,
366
+ titleSection2: config.titleSection2,
367
+ titleSection3: config.titleSection3,
368
+ uiConfig: config.uiConfig,
369
+ enablePagination: config.enablePagination,
370
+ pageSize: config.pageSize,
371
+ enableFilters: config.enableFilters,
372
+ maxFileSizeMb: config.maxFileSizeMb,
373
+ maxFilesCount: config.maxFilesCount,
374
+ createdAt: config.createdAt,
375
+ updatedAt: config.updatedAt
376
+ };
377
+ }
378
+ /**
379
+ * Преобразование FieldConfig в Field
380
+ */
381
+ convertToField(fieldConfig) {
382
+ return {
383
+ id: fieldConfig.id,
384
+ entityDefinitionId: fieldConfig.entityDefinitionId,
385
+ name: fieldConfig.name,
386
+ dbType: fieldConfig.dbType,
387
+ type: fieldConfig.type,
388
+ label: fieldConfig.label,
389
+ placeholder: fieldConfig.placeholder,
390
+ description: fieldConfig.description,
391
+ forEditPage: fieldConfig.forEditPage,
392
+ forCreatePage: fieldConfig.forCreatePage,
393
+ required: fieldConfig.required,
394
+ requiredText: fieldConfig.requiredText,
395
+ forEditPageDisabled: fieldConfig.forEditPageDisabled,
396
+ displayIndex: fieldConfig.displayIndex,
397
+ displayInTable: fieldConfig.displayInTable,
398
+ sectionIndex: fieldConfig.sectionIndex,
399
+ isOptionTitleField: fieldConfig.isOptionTitleField,
400
+ searchable: fieldConfig.searchable,
401
+ filterableInList: fieldConfig.filterableInList,
402
+ options: fieldConfig.options,
403
+ relatedEntityDefinitionId: fieldConfig.relatedEntityDefinitionId,
404
+ relationFieldId: fieldConfig.relationFieldId,
405
+ isRelationSource: fieldConfig.isRelationSource,
406
+ selectorRelationId: fieldConfig.selectorRelationId,
407
+ relationFieldName: fieldConfig.relationFieldName,
408
+ relationFieldLabel: fieldConfig.relationFieldLabel,
409
+ defaultStringValue: fieldConfig.defaultStringValue,
410
+ defaultNumberValue: fieldConfig.defaultNumberValue,
411
+ defaultBooleanValue: fieldConfig.defaultBooleanValue,
412
+ defaultDateValue: fieldConfig.defaultDateValue,
413
+ autoPopulate: fieldConfig.autoPopulate,
414
+ includeInSinglePma: fieldConfig.includeInSinglePma,
415
+ includeInListPma: fieldConfig.includeInListPma,
416
+ includeInSingleSa: fieldConfig.includeInSingleSa,
417
+ includeInListSa: fieldConfig.includeInListSa,
418
+ foreignKey: fieldConfig.foreignKey,
419
+ foreignKeyValue: fieldConfig.foreignKeyValue,
420
+ typeFieldName: fieldConfig.typeFieldName,
421
+ optionsFieldName: fieldConfig.optionsFieldName,
422
+ acceptFileTypes: fieldConfig.acceptFileTypes,
423
+ maxFileSize: fieldConfig.maxFileSize,
424
+ maxFiles: fieldConfig.maxFiles,
425
+ storageBucket: fieldConfig.storageBucket,
426
+ createdAt: fieldConfig.createdAt,
427
+ updatedAt: fieldConfig.updatedAt
428
+ };
429
+ }
430
+ /**
431
+ * Получить entity definition с полями и сгенерированным UI конфигом
432
+ * Использует кэш SDK для оптимизации (если дефиниция уже загружена, не делает повторный запрос)
433
+ *
434
+ * @param entityDefinitionId - ID entity definition
435
+ * @returns EntityDefinition, Fields и UI конфиг, или null если не найдено
436
+ */
437
+ async getEntityDefinitionWithUIConfig(entityDefinitionId) {
438
+ try {
439
+ const config = await this.getEntityDefinitionConfig(entityDefinitionId);
440
+ const entityDefinition = this.convertToEntityDefinition(config);
441
+ const fields = config.fields.map((f) => this.convertToField(f));
442
+ const uiConfig = generateUIConfig(entityDefinition, fields);
443
+ return {
444
+ entityDefinition,
445
+ fields,
446
+ uiConfig
447
+ };
448
+ } catch (error) {
449
+ if (error instanceof Error && error.message.includes("not found")) {
450
+ return null;
451
+ }
452
+ throw error;
453
+ }
454
+ }
455
+ /**
456
+ * Очистить кэш
457
+ */
458
+ clearCache() {
459
+ this.configCache.clear();
460
+ }
461
+ };
462
+
463
+ // src/utils/instance-utils.ts
464
+ function transformEntityInstance(row) {
465
+ return {
466
+ id: row.id,
467
+ entityDefinitionId: row.entity_definition_id,
468
+ projectId: row.project_id,
469
+ data: row.data || {},
470
+ createdAt: row.created_at,
471
+ updatedAt: row.updated_at
472
+ };
473
+ }
474
+ function flattenInstance(instance, fields, relationsAsIds = false) {
475
+ const result = {
476
+ id: instance.id,
477
+ entityDefinitionId: instance.entityDefinitionId,
478
+ projectId: instance.projectId,
479
+ createdAt: instance.createdAt,
480
+ updatedAt: instance.updatedAt
481
+ };
482
+ Object.entries(instance.data || {}).forEach(([key, value]) => {
483
+ result[key] = value;
484
+ });
485
+ if (instance.relations) {
486
+ Object.entries(instance.relations).forEach(
487
+ ([fieldName, relatedInstances]) => {
488
+ if (relationsAsIds) {
489
+ result[fieldName] = relatedInstances.map((inst) => inst.id);
490
+ } else {
491
+ const field = fields.find((f) => f.name === fieldName);
492
+ if ((field == null ? void 0 : field.dbType) === "manyToOne" || (field == null ? void 0 : field.dbType) === "oneToOne") {
493
+ result[fieldName] = relatedInstances[0] || null;
494
+ } else {
495
+ result[fieldName] = relatedInstances;
496
+ }
497
+ }
498
+ }
499
+ );
500
+ }
501
+ return result;
502
+ }
503
+
504
+ // src/errors.ts
505
+ var SDKError = class extends Error {
506
+ constructor(code, message, statusCode, details) {
507
+ super(message);
508
+ this.code = code;
509
+ this.statusCode = statusCode;
510
+ this.details = details;
511
+ this.name = "SDKError";
512
+ }
513
+ };
514
+ var NotFoundError = class extends SDKError {
515
+ constructor(resource, id) {
516
+ super(
517
+ "NOT_FOUND",
518
+ id ? `${resource} with id ${id} not found` : `${resource} not found`,
519
+ 404
520
+ );
521
+ }
522
+ };
523
+ var PermissionDeniedError = class extends SDKError {
524
+ constructor(action, resource) {
525
+ super(
526
+ "PERMISSION_DENIED",
527
+ `Permission denied: cannot ${action} ${resource}`,
528
+ 403
529
+ );
530
+ }
531
+ };
532
+ function handleSupabaseError(error) {
533
+ if (error.code === "PGRST116") {
534
+ throw new NotFoundError("Resource");
535
+ }
536
+ if (error.code === "23505") {
537
+ throw new SDKError("DUPLICATE_ENTRY", "Duplicate entry", 409);
538
+ }
539
+ if (error.code === "23503") {
540
+ throw new SDKError("FOREIGN_KEY_VIOLATION", "Foreign key violation", 400);
541
+ }
542
+ if (error.code === "42501") {
543
+ throw new PermissionDeniedError("access", "resource");
544
+ }
545
+ throw new SDKError(
546
+ "UNKNOWN_ERROR",
547
+ error.message || "Unknown error",
548
+ 500,
549
+ error
550
+ );
551
+ }
552
+ function handleInstanceError(error, instanceId) {
553
+ if ((error == null ? void 0 : error.code) === "PGRST116") {
554
+ throw new NotFoundError("Entity instance", instanceId);
555
+ }
556
+ if ((error == null ? void 0 : error.code) === "42501") {
557
+ throw new PermissionDeniedError("read", `entity instance ${instanceId}`);
558
+ }
559
+ handleSupabaseError(error);
560
+ }
561
+
562
+ // src/client.ts
563
+ var _PublicAPIClient = class _PublicAPIClient extends BasePublicAPIClient {
564
+ constructor(supabase, projectId, mode, options = {}) {
565
+ super(supabase, projectId, options);
566
+ this.mode = mode;
567
+ }
568
+ /**
569
+ * Создать SDK клиент напрямую с Supabase инстансом
570
+ * Используется для создания через фабричные функции createServerSDK/createClientSDK
571
+ *
572
+ * @param supabase - Supabase клиент
573
+ * @param projectId - ID проекта
574
+ * @param mode - Режим работы: 'server' для SSR, 'client' для браузера
575
+ * @param options - Опции SDK (кэширование и т.д.)
576
+ */
577
+ static create(supabase, projectId, mode, options = {}) {
578
+ const cacheKey = `${mode}-${projectId}-${JSON.stringify(options)}`;
579
+ if (this.instances.has(cacheKey)) {
580
+ return this.instances.get(cacheKey);
581
+ }
582
+ const client = new _PublicAPIClient(supabase, projectId, mode, options);
583
+ this.instances.set(cacheKey, client);
584
+ return client;
585
+ }
586
+ /**
587
+ * Получить режим работы клиента (для отладки)
588
+ */
589
+ getMode() {
590
+ return this.mode;
591
+ }
592
+ /**
593
+ * Получить один экземпляр
594
+ */
595
+ async getInstance(entityDefinitionId, id, params) {
596
+ var _a;
597
+ try {
598
+ const fields = await this.getFields(entityDefinitionId);
599
+ const { data: instance, error: instanceError } = await this.supabase.from("entity_instance").select("*").eq("id", id).eq("entity_definition_id", entityDefinitionId).eq("project_id", this.projectId).single();
600
+ if (instanceError || !instance) {
601
+ handleInstanceError(
602
+ instanceError || new Error("Instance not found"),
603
+ id
604
+ );
605
+ }
606
+ const transformedInstance = transformEntityInstance(instance);
607
+ if (transformedInstance.entityDefinitionId !== entityDefinitionId) {
608
+ throw new NotFoundError("Entity instance", id);
609
+ }
610
+ const relationFields = fields.filter(
611
+ (f) => f.dbType === "manyToMany" || f.dbType === "manyToOne" || f.dbType === "oneToMany" || f.dbType === "oneToOne"
612
+ );
613
+ const relations = {};
614
+ if (relationFields.length > 0) {
615
+ const relationFieldIds = relationFields.map((f) => f.id).filter((id2) => Boolean(id2));
616
+ if (relationFieldIds.length > 0) {
617
+ const { data: allRelations, error: relationsError } = await this.supabase.from("entity_relation").select("target_instance_id, relation_field_id").eq("source_instance_id", id).in("relation_field_id", relationFieldIds);
618
+ if (relationsError) {
619
+ throw new SDKError(
620
+ "RELATIONS_LOAD_ERROR",
621
+ `Failed to load relations for instance ${id}: ${relationsError.message}`,
622
+ 500,
623
+ relationsError
624
+ );
625
+ }
626
+ if (allRelations && allRelations.length > 0) {
627
+ const targetInstanceIds = [
628
+ ...new Set(allRelations.map((r) => r.target_instance_id))
629
+ ];
630
+ const { data: relatedInstances, error: instancesError } = await this.supabase.from("entity_instance").select("*").in("id", targetInstanceIds);
631
+ if (instancesError) {
632
+ throw new SDKError(
633
+ "RELATED_INSTANCES_LOAD_ERROR",
634
+ `Failed to load related instances for instance ${id}: ${instancesError.message}`,
635
+ 500,
636
+ instancesError
637
+ );
638
+ }
639
+ if (relatedInstances) {
640
+ const relatedInstancesMap = new Map(
641
+ relatedInstances.map((inst) => [
642
+ inst.id,
643
+ transformEntityInstance(inst)
644
+ ])
645
+ );
646
+ for (const relation of allRelations) {
647
+ const relationField = relationFields.find(
648
+ (f) => f.id === relation.relation_field_id
649
+ );
650
+ if (relationField) {
651
+ const relatedInstance = relatedInstancesMap.get(
652
+ relation.target_instance_id
653
+ );
654
+ if (relatedInstance) {
655
+ if (!relations[relationField.name]) {
656
+ relations[relationField.name] = [];
657
+ }
658
+ relations[relationField.name].push(
659
+ relatedInstance
660
+ );
661
+ }
662
+ }
663
+ }
664
+ }
665
+ }
666
+ }
667
+ }
668
+ const fileFields = fields.filter(
669
+ (f) => f.type === "files" || f.type === "images"
670
+ );
671
+ if (fileFields.length > 0) {
672
+ const { data: allFiles, error: filesError } = await this.supabase.from("entity_file").select("id, field_id").eq("entity_instance_id", id);
673
+ if (filesError) {
674
+ throw new SDKError(
675
+ "FILES_LOAD_ERROR",
676
+ `Failed to load files for instance ${id}: ${filesError.message}`,
677
+ 500,
678
+ filesError
679
+ );
680
+ }
681
+ if (allFiles) {
682
+ const filesByFieldId = /* @__PURE__ */ new Map();
683
+ allFiles.forEach((file) => {
684
+ if (file.field_id) {
685
+ if (!filesByFieldId.has(file.field_id)) {
686
+ filesByFieldId.set(file.field_id, []);
687
+ }
688
+ filesByFieldId.get(file.field_id).push(file.id);
689
+ }
690
+ });
691
+ fileFields.forEach((field) => {
692
+ const fileIds = filesByFieldId.get(field.id) || [];
693
+ if (fileIds.length > 0 || !transformedInstance.data[field.name]) {
694
+ transformedInstance.data[field.name] = fileIds;
695
+ }
696
+ });
697
+ }
698
+ }
699
+ const instanceWithRelations = __spreadProps(__spreadValues({}, transformedInstance), {
700
+ relations: Object.keys(relations).length > 0 ? relations : void 0
701
+ });
702
+ return flattenInstance(
703
+ instanceWithRelations,
704
+ fields.map((f) => ({ name: f.name, dbType: f.dbType })),
705
+ (_a = params == null ? void 0 : params.relationsAsIds) != null ? _a : false
706
+ );
707
+ } catch (error) {
708
+ if (error instanceof NotFoundError || error instanceof PermissionDeniedError || error instanceof SDKError) {
709
+ throw error;
710
+ }
711
+ handleSupabaseError(error);
712
+ }
713
+ }
714
+ /**
715
+ * Получить список экземпляров
716
+ * Поддерживает поиск, фильтры (JSONB и relation), пагинацию
717
+ */
718
+ async getInstances(entityDefinitionId, params) {
719
+ var _a, _b;
720
+ try {
721
+ const fields = await this.getFields(entityDefinitionId);
722
+ const page = (params == null ? void 0 : params.page) || 1;
723
+ const limit = (params == null ? void 0 : params.limit) || 20;
724
+ const offset = (page - 1) * limit;
725
+ const relationFieldsMap = /* @__PURE__ */ new Map();
726
+ fields.forEach((field) => {
727
+ if (field.relatedEntityDefinitionId && (field.dbType === "manyToMany" || field.dbType === "manyToOne" || field.dbType === "oneToMany" || field.dbType === "oneToOne")) {
728
+ relationFieldsMap.set(field.name, { fieldId: field.id });
729
+ }
730
+ });
731
+ const jsonbFilters = {};
732
+ const relationFiltersToApply = [];
733
+ if (params == null ? void 0 : params.filters) {
734
+ Object.entries(params.filters).forEach(([fieldName, values]) => {
735
+ if (values && values.length > 0) {
736
+ const relationField = relationFieldsMap.get(fieldName);
737
+ if (relationField) {
738
+ relationFiltersToApply.push({
739
+ fieldName,
740
+ fieldId: relationField.fieldId,
741
+ values
742
+ });
743
+ } else {
744
+ jsonbFilters[fieldName] = values;
745
+ }
746
+ }
747
+ });
748
+ }
749
+ let allowedInstanceIds = null;
750
+ if (relationFiltersToApply.length > 0) {
751
+ const filterModes = (params == null ? void 0 : params.relationFilterModes) || {};
752
+ const anyModeFilters = [];
753
+ const allModeFilters = [];
754
+ relationFiltersToApply.forEach((rf) => {
755
+ const mode = filterModes[rf.fieldName] || "any";
756
+ if (mode === "all") {
757
+ allModeFilters.push(rf);
758
+ } else {
759
+ anyModeFilters.push(rf);
760
+ }
761
+ });
762
+ const anyModeInstanceIds = anyModeFilters.length > 0 ? /* @__PURE__ */ new Set() : null;
763
+ if (anyModeInstanceIds !== null && anyModeFilters.length > 0) {
764
+ const orConditions = anyModeFilters.flatMap(
765
+ (rf) => rf.values.map(
766
+ (targetId) => `and(relation_field_id.eq.${rf.fieldId},target_instance_id.eq.${targetId})`
767
+ )
768
+ );
769
+ const { data: relations, error: relError } = await this.supabase.from("entity_relation").select("source_instance_id").or(orConditions.join(","));
770
+ if (relError) {
771
+ throw new SDKError(
772
+ "RELATION_FILTER_ERROR",
773
+ `Failed to apply relation filters (any mode): ${relError.message}`,
774
+ 500,
775
+ relError
776
+ );
777
+ } else if (relations) {
778
+ relations.forEach(
779
+ (r) => anyModeInstanceIds.add(r.source_instance_id)
780
+ );
781
+ }
782
+ }
783
+ const allModeInstanceIdSets = [];
784
+ for (const rf of allModeFilters) {
785
+ const { data: relations, error: relError } = await this.supabase.from("entity_relation").select("source_instance_id, target_instance_id").eq("relation_field_id", rf.fieldId).in("target_instance_id", rf.values);
786
+ if (relError) {
787
+ throw new SDKError(
788
+ "RELATION_FILTER_ERROR",
789
+ `Failed to apply relation filters (all mode): ${relError.message}`,
790
+ 500,
791
+ relError
792
+ );
793
+ }
794
+ if (relations) {
795
+ const sourceTargetMap = /* @__PURE__ */ new Map();
796
+ relations.forEach((r) => {
797
+ if (!sourceTargetMap.has(r.source_instance_id)) {
798
+ sourceTargetMap.set(r.source_instance_id, /* @__PURE__ */ new Set());
799
+ }
800
+ sourceTargetMap.get(r.source_instance_id).add(r.target_instance_id);
801
+ });
802
+ const validSources = /* @__PURE__ */ new Set();
803
+ const requiredTargets = new Set(rf.values);
804
+ sourceTargetMap.forEach((targets, sourceId) => {
805
+ const hasAll = [...requiredTargets].every((t) => targets.has(t));
806
+ if (hasAll) {
807
+ validSources.add(sourceId);
808
+ }
809
+ });
810
+ allModeInstanceIdSets.push(validSources);
811
+ }
812
+ }
813
+ if (anyModeInstanceIds !== null || allModeInstanceIdSets.length > 0) {
814
+ if (anyModeInstanceIds !== null && anyModeInstanceIds.size > 0) {
815
+ allowedInstanceIds = [...anyModeInstanceIds];
816
+ } else if (allModeInstanceIdSets.length > 0) {
817
+ allowedInstanceIds = [...allModeInstanceIdSets[0]];
818
+ }
819
+ if (allowedInstanceIds !== null && allModeInstanceIdSets.length > 0) {
820
+ for (const idSet of allModeInstanceIdSets) {
821
+ allowedInstanceIds = allowedInstanceIds.filter(
822
+ (id) => idSet.has(id)
823
+ );
824
+ }
825
+ }
826
+ }
827
+ if (allowedInstanceIds !== null && allowedInstanceIds.length === 0) {
828
+ return {
829
+ data: [],
830
+ pagination: {
831
+ page,
832
+ limit,
833
+ total: 0,
834
+ totalPages: 0,
835
+ hasPreviousPage: false,
836
+ hasNextPage: false
837
+ }
838
+ };
839
+ }
840
+ }
841
+ const searchFields = (() => {
842
+ const searchable = fields.filter((f) => f.searchable).map((f) => f.name);
843
+ return searchable.length > 0 ? searchable : ["name"];
844
+ })();
845
+ const searchTerm = ((_a = params == null ? void 0 : params.search) == null ? void 0 : _a.trim()) || null;
846
+ let data = null;
847
+ let error = null;
848
+ let count = null;
849
+ if (searchTerm) {
850
+ const rpcParams = {
851
+ p_entity_definition_id: entityDefinitionId,
852
+ p_project_id: this.projectId,
853
+ p_search_term: searchTerm,
854
+ p_search_fields: searchFields,
855
+ p_limit: limit,
856
+ p_offset: offset
857
+ };
858
+ const { data: rpcData, error: rpcError } = await this.supabase.rpc(
859
+ "search_entity_instances",
860
+ rpcParams
861
+ );
862
+ if (rpcError) {
863
+ error = rpcError;
864
+ } else if (rpcData && rpcData.length > 0) {
865
+ count = rpcData[0].total_count;
866
+ data = rpcData;
867
+ } else {
868
+ data = [];
869
+ count = 0;
870
+ }
871
+ } else {
872
+ let query = this.supabase.from("entity_instance").select("*", { count: "exact" }).eq("entity_definition_id", entityDefinitionId).eq("project_id", this.projectId);
873
+ if (allowedInstanceIds !== null && allowedInstanceIds.length > 0) {
874
+ query = query.in("id", allowedInstanceIds);
875
+ }
876
+ if (Object.keys(jsonbFilters).length > 0) {
877
+ Object.entries(jsonbFilters).forEach(([fieldName, values]) => {
878
+ if (values && values.length > 0) {
879
+ query = query.or(
880
+ values.map((v) => `data->>${fieldName}.eq.${v}`).join(",")
881
+ );
882
+ }
883
+ });
884
+ }
885
+ const sortBy = (params == null ? void 0 : params.sortBy) || "created_at";
886
+ const sortOrder = (params == null ? void 0 : params.sortOrder) || "desc";
887
+ query = query.order(sortBy, { ascending: sortOrder === "asc" });
888
+ query = query.range(offset, offset + limit - 1);
889
+ const result = await query;
890
+ data = result.data;
891
+ error = result.error;
892
+ count = result.count;
893
+ }
894
+ if (error) {
895
+ handleSupabaseError(error);
896
+ }
897
+ if (!data || data.length === 0) {
898
+ return {
899
+ data: [],
900
+ pagination: {
901
+ page,
902
+ limit,
903
+ total: count || 0,
904
+ totalPages: Math.ceil((count || 0) / limit),
905
+ hasPreviousPage: page > 1,
906
+ hasNextPage: page < Math.ceil((count || 0) / limit)
907
+ }
908
+ };
909
+ }
910
+ const transformedInstances = data.map(transformEntityInstance);
911
+ const relationsMap = /* @__PURE__ */ new Map();
912
+ const relationFieldsToLoad = fields.filter(
913
+ (f) => f.relatedEntityDefinitionId && (f.dbType === "manyToMany" || f.dbType === "manyToOne" || f.dbType === "oneToMany" || f.dbType === "oneToOne") && f.displayInTable
914
+ ).map((f) => f.name);
915
+ if (relationFieldsToLoad.length > 0 && transformedInstances.length > 0) {
916
+ const relationFields = relationFieldsToLoad.map((fieldName) => {
917
+ const field = fields.find(
918
+ (f) => f.name === fieldName && (f.dbType === "manyToMany" || f.dbType === "manyToOne" || f.dbType === "oneToMany" || f.dbType === "oneToOne")
919
+ );
920
+ return field ? { name: fieldName, field } : null;
921
+ }).filter(
922
+ (f) => f !== null
923
+ );
924
+ if (relationFields.length > 0) {
925
+ const instanceIds = transformedInstances.map((inst) => inst.id);
926
+ const relationFieldIds = relationFields.map((rf) => rf.field.id);
927
+ const { data: relationsWithInstances, error: rpcError } = await this.supabase.rpc(
928
+ "get_related_instances",
929
+ {
930
+ p_source_instance_ids: instanceIds,
931
+ p_relation_field_ids: relationFieldIds
932
+ }
933
+ );
934
+ if (rpcError) {
935
+ throw new SDKError(
936
+ "RELATIONS_LOAD_ERROR",
937
+ `Failed to load relations with instances: ${rpcError.message}`,
938
+ 500,
939
+ rpcError
940
+ );
941
+ }
942
+ if (relationsWithInstances && relationsWithInstances.length > 0) {
943
+ const relatedInstancesMap = new Map(
944
+ relationsWithInstances.map((row) => [
945
+ row.target_instance_id,
946
+ transformEntityInstance({
947
+ id: row.target_instance_id,
948
+ entity_definition_id: row.target_entity_definition_id,
949
+ project_id: row.target_project_id,
950
+ data: row.target_data,
951
+ created_at: row.target_created_at,
952
+ updated_at: row.target_updated_at
953
+ })
954
+ ])
955
+ );
956
+ const targetEntityDefinitionIds = [
957
+ ...new Set(
958
+ Array.from(relatedInstancesMap.values()).map(
959
+ (inst) => inst.entityDefinitionId
960
+ )
961
+ )
962
+ ];
963
+ const fieldsMap = /* @__PURE__ */ new Map();
964
+ await Promise.all(
965
+ targetEntityDefinitionIds.map(async (entityDefId) => {
966
+ const targetFields = await this.getFields(entityDefId);
967
+ fieldsMap.set(
968
+ entityDefId,
969
+ targetFields.map((f) => ({
970
+ name: f.name,
971
+ dbType: f.dbType
972
+ }))
973
+ );
974
+ })
975
+ );
976
+ for (const relationRow of relationsWithInstances) {
977
+ const instanceId = relationRow.source_instance_id;
978
+ const relationField = relationFields.find(
979
+ (rf) => rf.field.id === relationRow.relation_field_id
980
+ );
981
+ if (relationField) {
982
+ const relatedInstance = relatedInstancesMap.get(
983
+ relationRow.target_instance_id
984
+ );
985
+ if (relatedInstance) {
986
+ if (!relationsMap.has(instanceId)) {
987
+ relationsMap.set(instanceId, {});
988
+ }
989
+ const instanceRelations = relationsMap.get(instanceId);
990
+ if (!instanceRelations[relationField.name]) {
991
+ instanceRelations[relationField.name] = [];
992
+ }
993
+ const targetFields = fieldsMap.get(relatedInstance.entityDefinitionId) || [];
994
+ const flattenedRelated = flattenInstance(
995
+ relatedInstance,
996
+ targetFields,
997
+ (_b = params == null ? void 0 : params.relationsAsIds) != null ? _b : false
998
+ );
999
+ instanceRelations[relationField.name].push(flattenedRelated);
1000
+ }
1001
+ }
1002
+ }
1003
+ }
1004
+ }
1005
+ }
1006
+ const fileFields = fields.filter(
1007
+ (f) => f.type === "files" || f.type === "images"
1008
+ );
1009
+ if (fileFields.length > 0 && transformedInstances.length > 0) {
1010
+ const instanceIds = transformedInstances.map((inst) => inst.id);
1011
+ const fieldIds = fileFields.map((f) => f.id);
1012
+ const { data: allFiles, error: filesError } = await this.supabase.from("entity_file").select("id, entity_instance_id, field_id").in("entity_instance_id", instanceIds).in("field_id", fieldIds);
1013
+ if (filesError) {
1014
+ throw new SDKError(
1015
+ "FILES_LOAD_ERROR",
1016
+ `Failed to load files: ${filesError.message}`,
1017
+ 500,
1018
+ filesError
1019
+ );
1020
+ }
1021
+ if (allFiles) {
1022
+ const filesMap = /* @__PURE__ */ new Map();
1023
+ allFiles.forEach((file) => {
1024
+ if (file.field_id) {
1025
+ if (!filesMap.has(file.entity_instance_id)) {
1026
+ filesMap.set(file.entity_instance_id, /* @__PURE__ */ new Map());
1027
+ }
1028
+ const instanceFiles = filesMap.get(file.entity_instance_id);
1029
+ if (!instanceFiles.has(file.field_id)) {
1030
+ instanceFiles.set(file.field_id, []);
1031
+ }
1032
+ instanceFiles.get(file.field_id).push(file.id);
1033
+ }
1034
+ });
1035
+ transformedInstances.forEach((instance) => {
1036
+ const instanceFiles = filesMap.get(instance.id);
1037
+ if (instanceFiles) {
1038
+ fileFields.forEach((field) => {
1039
+ const fileIds = instanceFiles.get(field.id) || [];
1040
+ if (fileIds.length > 0 || !instance.data[field.name]) {
1041
+ instance.data[field.name] = fileIds;
1042
+ }
1043
+ });
1044
+ }
1045
+ });
1046
+ }
1047
+ }
1048
+ const instancesWithRelations = transformedInstances.map((instance) => {
1049
+ const instanceRelations = relationsMap.get(instance.id) || {};
1050
+ return __spreadProps(__spreadValues({}, instance), {
1051
+ relations: Object.keys(instanceRelations).length > 0 ? instanceRelations : void 0
1052
+ });
1053
+ });
1054
+ const flattenedInstances = instancesWithRelations.map(
1055
+ (inst) => {
1056
+ var _a2;
1057
+ return flattenInstance(
1058
+ inst,
1059
+ fields.map((f) => ({ name: f.name, dbType: f.dbType })),
1060
+ (_a2 = params == null ? void 0 : params.relationsAsIds) != null ? _a2 : false
1061
+ );
1062
+ }
1063
+ );
1064
+ const total = count || 0;
1065
+ const totalPages = Math.ceil(total / limit);
1066
+ return {
1067
+ data: flattenedInstances,
1068
+ pagination: {
1069
+ page,
1070
+ limit,
1071
+ total,
1072
+ totalPages,
1073
+ hasPreviousPage: page > 1,
1074
+ hasNextPage: page < totalPages
1075
+ }
1076
+ };
1077
+ } catch (error) {
1078
+ if (error instanceof NotFoundError || error instanceof PermissionDeniedError || error instanceof SDKError) {
1079
+ throw error;
1080
+ }
1081
+ handleSupabaseError(error);
1082
+ }
1083
+ }
1084
+ /**
1085
+ * Создать экземпляр сущности
1086
+ * Поддерживает создание с relations и автоматически устанавливает created_by
1087
+ */
1088
+ async createInstance(entityDefinitionId, data) {
1089
+ try {
1090
+ const fields = await this.getFields(entityDefinitionId);
1091
+ const {
1092
+ data: { user },
1093
+ error: userError
1094
+ } = await this.supabase.auth.getUser();
1095
+ if (userError) {
1096
+ console.warn(
1097
+ "[SDK] Could not get user for created_by:",
1098
+ userError.message
1099
+ );
1100
+ }
1101
+ const { data: instanceData, relations } = data;
1102
+ const { data: instance, error: instanceError } = await this.supabase.from("entity_instance").insert({
1103
+ entity_definition_id: entityDefinitionId,
1104
+ project_id: this.projectId,
1105
+ data: instanceData,
1106
+ created_by: (user == null ? void 0 : user.id) || null
1107
+ }).select().single();
1108
+ if (instanceError || !instance) {
1109
+ handleInstanceError(
1110
+ instanceError || new Error("Failed to create instance"),
1111
+ "new"
1112
+ );
1113
+ }
1114
+ const transformedInstance = transformEntityInstance(instance);
1115
+ if (relations && Object.keys(relations).length > 0) {
1116
+ const relationFields = fields.filter(
1117
+ (f) => f.dbType === "manyToMany" || f.dbType === "manyToOne" || f.dbType === "oneToMany" || f.dbType === "oneToOne"
1118
+ );
1119
+ const relationInserts = [];
1120
+ for (const [fieldName, targetInstanceIds] of Object.entries(
1121
+ relations
1122
+ )) {
1123
+ const field = relationFields.find((f) => f.name === fieldName);
1124
+ if (!field) {
1125
+ console.warn(
1126
+ `[SDK] Field ${fieldName} not found or not a relation field`
1127
+ );
1128
+ continue;
1129
+ }
1130
+ const ids = Array.isArray(targetInstanceIds) ? targetInstanceIds : targetInstanceIds ? [targetInstanceIds] : [];
1131
+ for (const targetInstanceId of ids) {
1132
+ if (targetInstanceId) {
1133
+ relationInserts.push({
1134
+ source_instance_id: transformedInstance.id,
1135
+ target_instance_id: targetInstanceId,
1136
+ relation_field_id: field.id,
1137
+ relation_type: field.dbType
1138
+ });
1139
+ }
1140
+ }
1141
+ }
1142
+ if (relationInserts.length > 0) {
1143
+ const { error: relationsError } = await this.supabase.from("entity_relation").insert(relationInserts);
1144
+ if (relationsError) {
1145
+ throw new SDKError(
1146
+ "RELATIONS_CREATE_ERROR",
1147
+ `Failed to create relations: ${relationsError.message}`,
1148
+ 500,
1149
+ relationsError
1150
+ );
1151
+ }
1152
+ }
1153
+ }
1154
+ return await this.getInstance(entityDefinitionId, transformedInstance.id);
1155
+ } catch (error) {
1156
+ if (error instanceof NotFoundError || error instanceof PermissionDeniedError || error instanceof SDKError) {
1157
+ throw error;
1158
+ }
1159
+ handleSupabaseError(error);
1160
+ }
1161
+ }
1162
+ async updateInstance(entityDefinitionId, id, data) {
1163
+ try {
1164
+ const { data: currentInstance, error: checkError } = await this.supabase.from("entity_instance").select("id, data, entity_definition_id, project_id").eq("id", id).eq("entity_definition_id", entityDefinitionId).eq("project_id", this.projectId).single();
1165
+ if (checkError || !currentInstance) {
1166
+ handleInstanceError(checkError || new Error("Instance not found"), id);
1167
+ }
1168
+ const fields = await this.getFields(entityDefinitionId);
1169
+ const { data: instanceData, relations } = data;
1170
+ const updatedData = __spreadValues(__spreadValues({}, currentInstance.data || {}), instanceData);
1171
+ const { data: updated, error: updateError } = await this.supabase.from("entity_instance").update({
1172
+ data: updatedData,
1173
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1174
+ }).eq("id", id).eq("entity_definition_id", entityDefinitionId).eq("project_id", this.projectId).select().single();
1175
+ if (updateError || !updated) {
1176
+ throw new SDKError(
1177
+ "UPDATE_ERROR",
1178
+ `Failed to update entity instance: ${(updateError == null ? void 0 : updateError.message) || "Update failed"}`,
1179
+ 500,
1180
+ updateError
1181
+ );
1182
+ }
1183
+ if (relations && Object.keys(relations).length > 0) {
1184
+ const relationFields = fields.filter(
1185
+ (f) => f.dbType === "manyToMany" || f.dbType === "manyToOne" || f.dbType === "oneToMany" || f.dbType === "oneToOne"
1186
+ );
1187
+ const fieldIds = Object.keys(relations).map((fieldName) => {
1188
+ const field = relationFields.find((f) => f.name === fieldName);
1189
+ return field == null ? void 0 : field.id;
1190
+ }).filter(Boolean);
1191
+ if (fieldIds.length > 0) {
1192
+ const { error: deleteError } = await this.supabase.from("entity_relation").delete().eq("source_instance_id", id).in("relation_field_id", fieldIds);
1193
+ if (deleteError) {
1194
+ throw new SDKError(
1195
+ "RELATIONS_DELETE_ERROR",
1196
+ `Failed to delete old relations: ${deleteError.message}`,
1197
+ 500,
1198
+ deleteError
1199
+ );
1200
+ }
1201
+ }
1202
+ const relationInserts = [];
1203
+ for (const [fieldName, targetInstanceIds] of Object.entries(
1204
+ relations
1205
+ )) {
1206
+ const field = relationFields.find((f) => f.name === fieldName);
1207
+ if (!field) {
1208
+ console.warn(
1209
+ `[SDK] Field ${fieldName} not found or not a relation field`
1210
+ );
1211
+ continue;
1212
+ }
1213
+ const ids = Array.isArray(targetInstanceIds) ? targetInstanceIds : targetInstanceIds ? [targetInstanceIds] : [];
1214
+ for (const targetInstanceId of ids) {
1215
+ if (targetInstanceId) {
1216
+ relationInserts.push({
1217
+ source_instance_id: id,
1218
+ target_instance_id: targetInstanceId,
1219
+ relation_field_id: field.id,
1220
+ relation_type: field.dbType
1221
+ });
1222
+ }
1223
+ }
1224
+ }
1225
+ if (relationInserts.length > 0) {
1226
+ const { error: relationsError } = await this.supabase.from("entity_relation").insert(relationInserts);
1227
+ if (relationsError) {
1228
+ throw new SDKError(
1229
+ "RELATIONS_CREATE_ERROR",
1230
+ `Failed to create relations: ${relationsError.message}`,
1231
+ 500,
1232
+ relationsError
1233
+ );
1234
+ }
1235
+ }
1236
+ }
1237
+ return await this.buildUpdatedInstance(updated, fields, relations || {});
1238
+ } catch (error) {
1239
+ if (error instanceof NotFoundError || error instanceof PermissionDeniedError || error instanceof SDKError) {
1240
+ throw error;
1241
+ }
1242
+ handleSupabaseError(error);
1243
+ }
1244
+ }
1245
+ /**
1246
+ * Формирует обновленный экземпляр из уже обновленных данных без повторной загрузки
1247
+ * Оптимизация: избегает лишних запросов после updateInstance
1248
+ * Загружает полные объекты relations для совместимости с форматом списка
1249
+ */
1250
+ async buildUpdatedInstance(updated, fields, relations) {
1251
+ const transformedInstance = transformEntityInstance(updated);
1252
+ const relationsMap = {};
1253
+ if (Object.keys(relations).length > 0) {
1254
+ const relationFields = fields.filter(
1255
+ (f) => f.dbType === "manyToMany" || f.dbType === "manyToOne" || f.dbType === "oneToMany" || f.dbType === "oneToOne"
1256
+ );
1257
+ const allTargetInstanceIds = [
1258
+ ...new Set(
1259
+ Object.values(relations).flatMap(
1260
+ (ids) => Array.isArray(ids) ? ids : [ids]
1261
+ )
1262
+ )
1263
+ ].filter(Boolean);
1264
+ if (allTargetInstanceIds.length > 0) {
1265
+ const { data: relatedInstances, error: instancesError } = await this.supabase.from("entity_instance").select("*").in("id", allTargetInstanceIds);
1266
+ if (instancesError) {
1267
+ console.warn(
1268
+ "[SDK] Failed to load related instances for buildUpdatedInstance:",
1269
+ instancesError.message
1270
+ );
1271
+ } else if (relatedInstances) {
1272
+ const relatedInstancesMap = new Map(
1273
+ relatedInstances.map((inst) => [
1274
+ inst.id,
1275
+ transformEntityInstance(inst)
1276
+ ])
1277
+ );
1278
+ const targetEntityDefinitionIds = [
1279
+ ...new Set(
1280
+ Array.from(relatedInstancesMap.values()).map(
1281
+ (inst) => inst.entityDefinitionId
1282
+ )
1283
+ )
1284
+ ];
1285
+ const fieldsMap = /* @__PURE__ */ new Map();
1286
+ await Promise.all(
1287
+ targetEntityDefinitionIds.map(async (entityDefId) => {
1288
+ const targetFields = await this.getFields(entityDefId);
1289
+ fieldsMap.set(
1290
+ entityDefId,
1291
+ targetFields.map((f) => ({
1292
+ name: f.name,
1293
+ dbType: f.dbType
1294
+ }))
1295
+ );
1296
+ })
1297
+ );
1298
+ for (const [fieldName, targetInstanceIds] of Object.entries(
1299
+ relations
1300
+ )) {
1301
+ const field = relationFields.find((f) => f.name === fieldName);
1302
+ if (field) {
1303
+ const ids = Array.isArray(targetInstanceIds) ? targetInstanceIds : targetInstanceIds ? [targetInstanceIds] : [];
1304
+ const relatedInstancesForField = [];
1305
+ for (const id of ids) {
1306
+ const relatedInstance = relatedInstancesMap.get(id);
1307
+ if (relatedInstance) {
1308
+ const targetFields = fieldsMap.get(relatedInstance.entityDefinitionId) || [];
1309
+ const flattenedRelated = flattenInstance(
1310
+ relatedInstance,
1311
+ targetFields,
1312
+ false
1313
+ // relationsAsIds = false для совместимости со списком
1314
+ );
1315
+ relatedInstancesForField.push(flattenedRelated);
1316
+ }
1317
+ }
1318
+ if (relatedInstancesForField.length > 0) {
1319
+ relationsMap[fieldName] = relatedInstancesForField;
1320
+ }
1321
+ }
1322
+ }
1323
+ }
1324
+ }
1325
+ }
1326
+ const instanceWithRelations = __spreadProps(__spreadValues({}, transformedInstance), {
1327
+ relations: Object.keys(relationsMap).length > 0 ? relationsMap : void 0
1328
+ });
1329
+ return flattenInstance(
1330
+ instanceWithRelations,
1331
+ fields.map((f) => ({ name: f.name, dbType: f.dbType })),
1332
+ false
1333
+ // relationsAsIds = false - возвращаем полные объекты как в списке
1334
+ );
1335
+ }
1336
+ /**
1337
+ * Удалить экземпляр сущности
1338
+ * Связи удалятся автоматически через ON DELETE CASCADE
1339
+ */
1340
+ async deleteInstance(entityDefinitionId, id) {
1341
+ try {
1342
+ const { data: instance, error: checkError } = await this.supabase.from("entity_instance").select("id, entity_definition_id, project_id").eq("id", id).eq("entity_definition_id", entityDefinitionId).eq("project_id", this.projectId).single();
1343
+ if (checkError || !instance) {
1344
+ handleInstanceError(checkError || new Error("Instance not found"), id);
1345
+ }
1346
+ const { error: deleteError } = await this.supabase.from("entity_instance").delete().eq("id", id).eq("entity_definition_id", entityDefinitionId).eq("project_id", this.projectId);
1347
+ if (deleteError) {
1348
+ throw new SDKError(
1349
+ "DELETE_ERROR",
1350
+ `Failed to delete entity instance: ${deleteError.message}`,
1351
+ 500,
1352
+ deleteError
1353
+ );
1354
+ }
1355
+ } catch (error) {
1356
+ if (error instanceof NotFoundError || error instanceof PermissionDeniedError || error instanceof SDKError) {
1357
+ throw error;
1358
+ }
1359
+ handleSupabaseError(error);
1360
+ }
1361
+ }
1362
+ async signIn(email, password) {
1363
+ throw new Error("signIn: Not implemented yet");
1364
+ }
1365
+ async signUp(data) {
1366
+ throw new Error("signUp: Not implemented yet");
1367
+ }
1368
+ async signOut() {
1369
+ throw new Error("signOut: Not implemented yet");
1370
+ }
1371
+ async getCurrentUser() {
1372
+ throw new Error("getCurrentUser: Not implemented yet");
1373
+ }
1374
+ };
1375
+ _PublicAPIClient.instances = /* @__PURE__ */ new Map();
1376
+ var PublicAPIClient = _PublicAPIClient;
1377
+
1378
+ // src/server.ts
1379
+ async function createServerSDK(projectId, config, options) {
1380
+ const supabase = createServerClient(
1381
+ config.supabaseUrl,
1382
+ config.supabaseAnonKey,
1383
+ config.cookies ? { cookies: config.cookies } : void 0
1384
+ );
1385
+ return PublicAPIClient.create(supabase, projectId, "server", options);
1386
+ }
1387
+
1388
+ export { createServerSDK };
1389
+ //# sourceMappingURL=server.mjs.map
1390
+ //# sourceMappingURL=server.mjs.map