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