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