@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/LICENSE +22 -0
- package/README.md +234 -0
- package/dist/client-DqqjGYgA.d.mts +1175 -0
- package/dist/client-DqqjGYgA.d.ts +1175 -0
- package/dist/index.d.mts +108 -0
- package/dist/index.d.ts +108 -0
- package/dist/index.js +1521 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1502 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server.d.mts +46 -0
- package/dist/server.d.ts +46 -0
- package/dist/server.js +1392 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +1390 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +64 -0
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
|