@fayz-ai/core 0.11.0 → 0.14.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.
Files changed (59) hide show
  1. package/dist/{chunk-BEGXYHS4.js → chunk-7GJTLBG3.js} +3 -3
  2. package/dist/{chunk-BEGXYHS4.js.map → chunk-7GJTLBG3.js.map} +1 -1
  3. package/dist/{chunk-V53GR4UT.js → chunk-IKZR3WSK.js} +3 -3
  4. package/dist/{chunk-V53GR4UT.js.map → chunk-IKZR3WSK.js.map} +1 -1
  5. package/dist/{chunk-LARZWDHA.js → chunk-KFBEKFII.js} +5 -4
  6. package/dist/chunk-KFBEKFII.js.map +1 -0
  7. package/dist/chunk-S5FOFKR4.js +1618 -0
  8. package/dist/chunk-S5FOFKR4.js.map +1 -0
  9. package/dist/{chunk-TDI6F6PT.js → chunk-X4FRQR2R.js} +10 -3
  10. package/dist/chunk-X4FRQR2R.js.map +1 -0
  11. package/dist/data/cached.d.ts +5 -1
  12. package/dist/data/cached.d.ts.map +1 -1
  13. package/dist/data/errors.d.ts +44 -0
  14. package/dist/data/errors.d.ts.map +1 -0
  15. package/dist/data/index.d.ts +8 -0
  16. package/dist/data/index.d.ts.map +1 -1
  17. package/dist/data/index.js +2 -2
  18. package/dist/data/offline.d.ts +45 -0
  19. package/dist/data/offline.d.ts.map +1 -0
  20. package/dist/data/query.d.ts +91 -0
  21. package/dist/data/query.d.ts.map +1 -0
  22. package/dist/data/resolve.d.ts.map +1 -1
  23. package/dist/data/types.d.ts +13 -0
  24. package/dist/data/types.d.ts.map +1 -1
  25. package/dist/events/bus.d.ts +12 -0
  26. package/dist/events/bus.d.ts.map +1 -0
  27. package/dist/events/index.d.ts +3 -11
  28. package/dist/events/index.d.ts.map +1 -1
  29. package/dist/i18n/index.d.ts.map +1 -1
  30. package/dist/i18n/index.js +1 -1
  31. package/dist/i18n/shell-translations.en.d.ts.map +1 -1
  32. package/dist/i18n/shell-translations.pt-br.d.ts.map +1 -1
  33. package/dist/index.d.ts +13 -3
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +191 -268
  36. package/dist/index.js.map +1 -1
  37. package/dist/integrations/index.js +2 -2
  38. package/dist/lib/cache-idb.d.ts +54 -0
  39. package/dist/lib/cache-idb.d.ts.map +1 -0
  40. package/dist/lib/cache.d.ts +2 -0
  41. package/dist/lib/cache.d.ts.map +1 -1
  42. package/dist/search/engine.d.ts.map +1 -1
  43. package/dist/search/types.d.ts +7 -0
  44. package/dist/search/types.d.ts.map +1 -1
  45. package/dist/{shell-translations.pt-br-BM5ZFSKJ.js → shell-translations.pt-br-PTOWYXNA.js} +7 -2
  46. package/dist/shell-translations.pt-br-PTOWYXNA.js.map +1 -0
  47. package/dist/testing/index.js +3 -3
  48. package/dist/types/analytics.d.ts +22 -0
  49. package/dist/types/analytics.d.ts.map +1 -1
  50. package/dist/types/crud.d.ts +20 -0
  51. package/dist/types/crud.d.ts.map +1 -1
  52. package/dist/types/plugins.d.ts +11 -0
  53. package/dist/types/plugins.d.ts.map +1 -1
  54. package/package.json +5 -5
  55. package/dist/chunk-LARZWDHA.js.map +0 -1
  56. package/dist/chunk-TDI6F6PT.js.map +0 -1
  57. package/dist/chunk-UVKH6RDX.js +0 -739
  58. package/dist/chunk-UVKH6RDX.js.map +0 -1
  59. package/dist/shell-translations.pt-br-BM5ZFSKJ.js.map +0 -1
@@ -1,739 +0,0 @@
1
- import { getActiveTenantId, getSupabaseClientOptional, parseRangeFilter, matchesRange, applyRangeToQuery, isFilterObject, globalCache, stableKey, createSupabaseProvider } from './chunk-LARZWDHA.js';
2
- import { declaredCustomFields, validateEntityRecordData } from './chunk-EWKX4VUM.js';
3
-
4
- // src/data/count.ts
5
- var TTL_MS = 15e3;
6
- var cache = /* @__PURE__ */ new Map();
7
- function cacheKey(table, tenantId, kind, period) {
8
- return `${tenantId}::${table}::${kind ?? ""}::${period}`;
9
- }
10
- function startOfMonthISO() {
11
- const now = /* @__PURE__ */ new Date();
12
- return new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
13
- }
14
- var COUNT_TIMEOUT_MS = 5e3;
15
- async function countByTenant(table, options = {}) {
16
- const tenantId = options.tenantId ?? getActiveTenantId();
17
- if (!tenantId) return 0;
18
- const period = options.period ?? "total";
19
- const key = cacheKey(table, tenantId, options.kind, period);
20
- if (!options.fresh) {
21
- const hit = cache.get(key);
22
- if (hit && hit.expires > Date.now()) return hit.value;
23
- }
24
- const client = getSupabaseClientOptional();
25
- if (!client) return 0;
26
- let query = client.from(table).select("*", { count: "exact", head: true }).eq("tenant_id", tenantId);
27
- if (options.kind) {
28
- query = query.eq("kind", options.kind);
29
- }
30
- if (period === "month") {
31
- query = query.gte("created_at", startOfMonthISO());
32
- }
33
- let timer;
34
- const timeout = new Promise((resolve) => {
35
- timer = setTimeout(() => resolve({ count: null, error: "timeout", timedOut: true }), COUNT_TIMEOUT_MS);
36
- });
37
- const settled = await Promise.race([
38
- query,
39
- timeout
40
- ]);
41
- if (timer) clearTimeout(timer);
42
- const { count, error } = settled;
43
- const value = error ? 0 : count ?? 0;
44
- if (!settled.timedOut) {
45
- cache.set(key, { value, expires: Date.now() + TTL_MS });
46
- }
47
- return value;
48
- }
49
- function invalidateCount(tenantOrKey) {
50
- if (!tenantOrKey) {
51
- cache.clear();
52
- return;
53
- }
54
- for (const key of cache.keys()) {
55
- if (key.includes(tenantOrKey)) cache.delete(key);
56
- }
57
- }
58
-
59
- // src/data/platform-api.ts
60
- function resolveTenantId(config) {
61
- const tenantId = config?.tenantId;
62
- return typeof tenantId === "function" ? tenantId() : tenantId;
63
- }
64
- async function resolveRuntimeToken(config) {
65
- const runtimeToken = config?.runtimeToken;
66
- return typeof runtimeToken === "function" ? runtimeToken() : runtimeToken;
67
- }
68
- function trimTrailingSlash(value) {
69
- return value.replace(/\/+$/, "");
70
- }
71
- function appendQuery(url, query, tenantId, tenantIdColumn = "tenant_id", searchColumns = []) {
72
- const filters = normalizeFilters(query.filters);
73
- if (query.search && searchColumns[0]) {
74
- filters.push({ column: searchColumns[0], operator: "ilike", value: `%${query.search}%` });
75
- }
76
- if (query.sortBy) url.searchParams.set("sortColumn", query.sortBy);
77
- if (query.sortDir) url.searchParams.set("sortDirection", query.sortDir);
78
- if (query.page != null) url.searchParams.set("page", String(query.page));
79
- if (query.pageSize != null) url.searchParams.set("limit", String(query.pageSize));
80
- if (tenantId) filters.push({ column: tenantIdColumn, operator: "eq", value: tenantId });
81
- if (filters.length > 0) url.searchParams.set("filters", JSON.stringify(filters));
82
- }
83
- async function parseResponse(response) {
84
- if (!response.ok) {
85
- const text = await response.text().catch(() => "");
86
- throw new Error(`[@fayz-ai/core] Fayz API request failed (${response.status}): ${text || response.statusText}`);
87
- }
88
- return response.json();
89
- }
90
- function isFilterOperator(value) {
91
- return typeof value === "string" && [
92
- "eq",
93
- "neq",
94
- "gt",
95
- "lt",
96
- "gte",
97
- "lte",
98
- "like",
99
- "ilike",
100
- "is_null",
101
- "is_not_null"
102
- ].includes(value);
103
- }
104
- function isRecord(value) {
105
- return typeof value === "object" && value !== null && !Array.isArray(value);
106
- }
107
- function normalizeFilters(filters) {
108
- if (!filters) return [];
109
- return Object.entries(filters).flatMap(([column, raw]) => {
110
- if (isRecord(raw) && isFilterOperator(raw.operator)) {
111
- return [{ column, operator: raw.operator, value: raw.value }];
112
- }
113
- const range = parseRangeFilter(raw);
114
- if (range) {
115
- return ["gte", "gt", "lte", "lt"].filter((op) => range[op] !== void 0).map((op) => ({ column, operator: op, value: range[op] }));
116
- }
117
- return [{ column, operator: "eq", value: raw }];
118
- });
119
- }
120
- function createFayzApiProvider(entityOrTable, config = {}) {
121
- const fetcher = config.fetcher ?? fetch;
122
- const baseUrl = trimTrailingSlash(config.baseUrl ?? "");
123
- const projectId = config.projectId;
124
- const table = config.table ?? entityOrTable;
125
- const idColumn = config.idColumn ?? "id";
126
- async function headers(runtimeToken) {
127
- return {
128
- "Content-Type": "application/json",
129
- ...config.headers ? await config.headers() : {},
130
- ...runtimeToken ? { Authorization: `Bearer ${runtimeToken}` } : {}
131
- };
132
- }
133
- function tableRowsUrl(runtime = false) {
134
- const prefix = baseUrl || (typeof window !== "undefined" ? window.location.origin : "http://localhost");
135
- const encodedProjectId = encodeURIComponent(projectId ?? "current");
136
- const encodedTable = encodeURIComponent(table);
137
- const route = runtime ? `/api/v1/runtime/projects/${encodedProjectId}/database/tables/${encodedTable}/rows` : `/api/projects/${encodedProjectId}/database/tables/${encodedTable}/rows`;
138
- const url = new URL(route, prefix);
139
- if (config.schema) url.searchParams.set("schema", config.schema);
140
- return url;
141
- }
142
- function withTenant(data, runtime = false) {
143
- if (runtime) return data;
144
- const tenantId = resolveTenantId(config);
145
- if (!tenantId || config.tenantIdColumn === false) return data;
146
- return { ...data, [config.tenantIdColumn ?? "tenant_id"]: tenantId };
147
- }
148
- function primaryKeys(id) {
149
- return { [idColumn]: id };
150
- }
151
- async function listRows(query) {
152
- const runtimeToken = await resolveRuntimeToken(config);
153
- const runtime = Boolean(runtimeToken);
154
- const url = tableRowsUrl(runtime);
155
- appendQuery(
156
- url,
157
- query,
158
- runtime || config.tenantIdColumn === false ? void 0 : resolveTenantId(config),
159
- config.tenantIdColumn || void 0,
160
- config.searchColumns
161
- );
162
- const result = await parseResponse(await fetcher(url, { headers: await headers(runtimeToken) }));
163
- return { data: result.rows, total: result.total };
164
- }
165
- async function assertTenantScopedRowExists(id, runtime = false) {
166
- if (runtime) return;
167
- const tenantId = config.tenantIdColumn === false ? void 0 : resolveTenantId(config);
168
- if (!tenantId) return;
169
- const result = await listRows({ filters: { [idColumn]: id }, page: 1, pageSize: 1 });
170
- if (result.data.length === 0) {
171
- throw new Error(`[@fayz-ai/core] Row "${id}" was not found for the active tenant.`);
172
- }
173
- }
174
- return {
175
- async list(query) {
176
- return listRows(query);
177
- },
178
- async create(data) {
179
- const runtimeToken = await resolveRuntimeToken(config);
180
- const runtime = Boolean(runtimeToken);
181
- return parseResponse(await fetcher(tableRowsUrl(runtime), {
182
- method: "POST",
183
- headers: await headers(runtimeToken),
184
- body: JSON.stringify(withTenant(data, runtime))
185
- }));
186
- },
187
- async update(id, data) {
188
- const runtimeToken = await resolveRuntimeToken(config);
189
- const runtime = Boolean(runtimeToken);
190
- await assertTenantScopedRowExists(id, runtime);
191
- return parseResponse(await fetcher(tableRowsUrl(runtime), {
192
- method: "PATCH",
193
- headers: await headers(runtimeToken),
194
- body: JSON.stringify({ primaryKeys: primaryKeys(id), data })
195
- }));
196
- },
197
- async remove(id) {
198
- const runtimeToken = await resolveRuntimeToken(config);
199
- const runtime = Boolean(runtimeToken);
200
- await assertTenantScopedRowExists(id, runtime);
201
- await parseResponse(await fetcher(tableRowsUrl(runtime), {
202
- method: "DELETE",
203
- headers: await headers(runtimeToken),
204
- body: JSON.stringify({ rows: [primaryKeys(id)] })
205
- }));
206
- },
207
- async removeMany(ids) {
208
- if (!ids.length) return;
209
- const runtimeToken = await resolveRuntimeToken(config);
210
- const runtime = Boolean(runtimeToken);
211
- for (const id of ids) await assertTenantScopedRowExists(id, runtime);
212
- await parseResponse(await fetcher(tableRowsUrl(runtime), {
213
- method: "DELETE",
214
- headers: await headers(runtimeToken),
215
- body: JSON.stringify({ rows: ids.map((id) => primaryKeys(id)) })
216
- }));
217
- }
218
- };
219
- }
220
-
221
- // src/data/mock.ts
222
- function createMockProvider(entityDefOrSearchKeys, initialData = []) {
223
- let items = [...initialData];
224
- const searchableKeys = Array.isArray(entityDefOrSearchKeys) ? entityDefOrSearchKeys : entityDefOrSearchKeys.fields.filter((f) => f.searchable).map((f) => f.key);
225
- const defaultSort = Array.isArray(entityDefOrSearchKeys) ? void 0 : entityDefOrSearchKeys.defaultSort;
226
- const defaultSortDir = Array.isArray(entityDefOrSearchKeys) ? "asc" : entityDefOrSearchKeys.defaultSortDir ?? "asc";
227
- return {
228
- async list(query) {
229
- let filtered = [...items];
230
- if (query.search && searchableKeys.length > 0) {
231
- const term = query.search.toLowerCase();
232
- filtered = filtered.filter(
233
- (item) => searchableKeys.some((key) => {
234
- const val = item[key];
235
- return typeof val === "string" && val.toLowerCase().includes(term);
236
- })
237
- );
238
- }
239
- if (query.filters) {
240
- for (const [key, val] of Object.entries(query.filters)) {
241
- if (val == null || val === "") continue;
242
- const range = parseRangeFilter(val);
243
- filtered = range ? filtered.filter((item) => matchesRange(item[key], range)) : filtered.filter((item) => item[key] === val);
244
- }
245
- }
246
- const sortBy = query.sortBy ?? defaultSort;
247
- const sortDir = query.sortDir ?? defaultSortDir;
248
- if (sortBy) {
249
- filtered.sort((a, b) => {
250
- const av = a[sortBy] ?? "";
251
- const bv = b[sortBy] ?? "";
252
- const cmp = typeof av === "number" && typeof bv === "number" ? av - bv : String(av).localeCompare(String(bv));
253
- return sortDir === "desc" ? -cmp : cmp;
254
- });
255
- }
256
- const total = filtered.length;
257
- const page = query.page ?? 1;
258
- const pageSize = query.pageSize ?? 50;
259
- const start = (page - 1) * pageSize;
260
- const data = filtered.slice(start, start + pageSize);
261
- return { data, total };
262
- },
263
- async create(data) {
264
- const now = (/* @__PURE__ */ new Date()).toISOString();
265
- const item = {
266
- ...data,
267
- id: crypto.randomUUID(),
268
- tenantId: "mock-tenant",
269
- createdAt: now,
270
- updatedAt: now
271
- };
272
- items.unshift(item);
273
- return item;
274
- },
275
- async update(id, data) {
276
- const idx = items.findIndex((i) => i.id === id);
277
- if (idx === -1) throw new Error(`[@fayz-ai/core] Mock provider: item not found: ${id}`);
278
- items[idx] = { ...items[idx], ...data, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
279
- return items[idx];
280
- },
281
- async remove(id) {
282
- items = items.filter((i) => i.id !== id);
283
- },
284
- async removeMany(ids) {
285
- const drop = new Set(ids);
286
- items = items.filter((i) => !drop.has(i.id));
287
- }
288
- };
289
- }
290
-
291
- // src/data/archetype.ts
292
- var HAS_KIND = /* @__PURE__ */ new Set(["person", "category", "order", "transaction", "schedule", "location"]);
293
- var ARCHETYPE_CONFIG = {
294
- person: { table: "people", fkColumn: "person_id" },
295
- category: { table: "categories", fkColumn: "category_id" },
296
- product: { table: "products", fkColumn: "product_id" },
297
- service: { table: "services", fkColumn: "service_id" },
298
- location: { table: "locations", fkColumn: "location_id" },
299
- order: { table: "orders", fkColumn: "order_id" },
300
- transaction: { table: "transactions", fkColumn: "transaction_id" },
301
- schedule: { table: "schedules", fkColumn: "schedule_id" }
302
- };
303
- var CUSTOM_FIELDS_COLUMN = "custom_fields";
304
- var CUSTOM_FIELD_FILTER_PREFIX = "custom.";
305
- var ARCHETYPE_COLUMNS = {
306
- person: /* @__PURE__ */ new Set(["name", "email", "phone", "document_number", "avatar_url", "date_of_birth", "address", "city", "state", "country", "postal_code", "tags", "is_active", "notes", "metadata", CUSTOM_FIELDS_COLUMN]),
307
- category: /* @__PURE__ */ new Set(["name", "slug", "parent_id", "icon", "color", "sort_order", "is_active", "metadata", CUSTOM_FIELDS_COLUMN]),
308
- product: /* @__PURE__ */ new Set(["name", "description", "sku", "price", "cost", "currency", "unit", "image_url", "stock", "min_stock", "status", "is_active", "tags", "metadata", "category_id", CUSTOM_FIELDS_COLUMN]),
309
- service: /* @__PURE__ */ new Set(["name", "description", "price", "cost", "currency", "duration_minutes", "image_url", "status", "is_active", "tags", "metadata", "category_id", CUSTOM_FIELDS_COLUMN]),
310
- location: /* @__PURE__ */ new Set(["name", "email", "phone", "address", "city", "state", "country", "postal_code", "is_headquarters", "is_active", "tags", "notes", "metadata", CUSTOM_FIELDS_COLUMN]),
311
- order: /* @__PURE__ */ new Set(["reference_number", "status", "party_id", "assignee_id", "location_id", "subtotal", "discount", "tax", "total", "currency", "due_at", "completed_at", "notes", "tags", "metadata", CUSTOM_FIELDS_COLUMN]),
312
- transaction: /* @__PURE__ */ new Set(["order_id", "party_id", "amount", "currency", "payment_method", "reference", "status", "transacted_at", "notes", "metadata", CUSTOM_FIELDS_COLUMN]),
313
- schedule: /* @__PURE__ */ new Set(["assignee_id", "location_id", "day_of_week", "specific_date", "starts_at", "ends_at", "is_active", "metadata", CUSTOM_FIELDS_COLUMN])
314
- };
315
- function camelToSnake(str) {
316
- return str.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase());
317
- }
318
- function snakeToCamel(str) {
319
- return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
320
- }
321
- function mapRow(row) {
322
- const result = {};
323
- for (const [key, value] of Object.entries(row)) {
324
- result[snakeToCamel(key)] = value;
325
- }
326
- return result;
327
- }
328
- function createArchetypeProvider(config) {
329
- const ac = ARCHETYPE_CONFIG[config.archetype];
330
- const archetypeColumns = ARCHETYPE_COLUMNS[config.archetype];
331
- const { fkColumn } = ac;
332
- function getClient() {
333
- const supabase = getSupabaseClientOptional();
334
- if (!supabase) throw new Error("Supabase client not available");
335
- return supabase;
336
- }
337
- function coreClient() {
338
- return getClient();
339
- }
340
- function splitFields(data) {
341
- const archetypeData = {};
342
- const projectData = {};
343
- for (const [key, value] of Object.entries(data)) {
344
- if (["id", "createdAt", "updatedAt", "createdBy", "tenantId", fkColumn].includes(key)) continue;
345
- const snakeKey = camelToSnake(key);
346
- if (archetypeColumns.has(snakeKey)) {
347
- archetypeData[snakeKey] = value;
348
- } else {
349
- projectData[snakeKey] = value;
350
- }
351
- }
352
- return { archetypeData, projectData };
353
- }
354
- const VIEW_MAP = {
355
- clients: "v_clients",
356
- staff_members: "v_staff"
357
- };
358
- const viewName = VIEW_MAP[config.projectTable] ?? `v_${config.projectTable}`;
359
- const isPure = config.projectTable === ac.table;
360
- return {
361
- async list(query) {
362
- const tenantId = config.tenantId();
363
- if (!tenantId) return { data: [], total: 0 };
364
- const countOpts = query.countMode === "none" ? void 0 : { count: "exact" };
365
- let q = isPure ? coreClient().from(ac.table).select("*", countOpts).eq("tenant_id", tenantId) : getClient().from(viewName).select("*", countOpts).eq("tenant_id", tenantId);
366
- if (isPure && HAS_KIND.has(config.archetype)) {
367
- q = q.eq("kind", config.archetypeKind);
368
- }
369
- if (query.filters) {
370
- for (const [key, val] of Object.entries(query.filters)) {
371
- if (val == null || val === "") continue;
372
- const col = key.startsWith(CUSTOM_FIELD_FILTER_PREFIX) ? `${CUSTOM_FIELDS_COLUMN}->>${key.slice(CUSTOM_FIELD_FILTER_PREFIX.length)}` : camelToSnake(key);
373
- const range = parseRangeFilter(val);
374
- if (range) {
375
- q = applyRangeToQuery(q, col, range);
376
- continue;
377
- }
378
- if (isFilterObject(val)) {
379
- throw new Error(
380
- `[@fayz-ai/core] Filter "${key}" is an object with no recognised bound. Use {from,to} / {gte,lte,gt,lt} for ranges, or a plain value for equality.`
381
- );
382
- }
383
- q = q.eq(col, val);
384
- }
385
- }
386
- if (query.search && config.searchColumns && config.searchColumns.length > 0) {
387
- const term = `%${query.search}%`;
388
- const orClauses = config.searchColumns.map((col) => camelToSnake(col)).map((col) => `${col}.ilike.${term}`);
389
- if (orClauses.length > 0) {
390
- q = q.or(orClauses.join(","));
391
- }
392
- }
393
- if (query.sortBy) {
394
- q = q.order(camelToSnake(query.sortBy), { ascending: query.sortDir !== "desc" });
395
- } else {
396
- q = q.order("created_at", { ascending: false });
397
- }
398
- const page = query.page ?? 1;
399
- const pageSize = query.pageSize ?? 50;
400
- const from = (page - 1) * pageSize;
401
- q = q.range(from, from + pageSize - 1);
402
- const { data, error, count } = await q;
403
- if (error) throw error;
404
- const rows = (data ?? []).map((row) => mapRow(row));
405
- return { data: rows, total: count ?? 0 };
406
- },
407
- async create(data) {
408
- const tenantId = config.tenantId();
409
- if (!tenantId) throw new Error("No tenant selected");
410
- const { archetypeData, projectData } = splitFields(data);
411
- archetypeData.tenant_id = tenantId;
412
- if (HAS_KIND.has(config.archetype)) {
413
- archetypeData.kind = config.archetypeKind;
414
- }
415
- const { data: archetypeRow, error: archetypeError } = await coreClient().from(ac.table).insert(archetypeData).select().single();
416
- if (archetypeError) throw archetypeError;
417
- const archetypeId = archetypeRow.id;
418
- if (!isPure) {
419
- projectData[fkColumn] = archetypeId;
420
- projectData.tenant_id = tenantId;
421
- const { error: projectError } = await getClient().from(config.projectTable).insert(projectData);
422
- if (projectError) {
423
- await coreClient().from(ac.table).delete().eq("id", archetypeId);
424
- throw projectError;
425
- }
426
- }
427
- return { id: archetypeId, ...archetypeRow, ...isPure ? {} : projectData };
428
- },
429
- async update(id, data) {
430
- const { archetypeData, projectData } = splitFields(data);
431
- if (Object.keys(archetypeData).length > 0) {
432
- const { error } = await coreClient().from(ac.table).update(archetypeData).eq("id", id);
433
- if (error) throw error;
434
- }
435
- if (!isPure && Object.keys(projectData).length > 0) {
436
- const { error } = await getClient().from(config.projectTable).update(projectData).eq(fkColumn, id);
437
- if (error) throw error;
438
- }
439
- const { data: archetypeRow } = await coreClient().from(ac.table).select("*").eq("id", id).single();
440
- if (isPure) {
441
- return mapRow({ ...archetypeRow, id });
442
- }
443
- const { data: projectRow } = await getClient().from(config.projectTable).select("*").eq(fkColumn, id).single();
444
- const flat = { ...archetypeRow, ...projectRow, id };
445
- if (archetypeRow && CUSTOM_FIELDS_COLUMN in archetypeRow) {
446
- flat[CUSTOM_FIELDS_COLUMN] = archetypeRow[CUSTOM_FIELDS_COLUMN];
447
- }
448
- delete flat[fkColumn];
449
- return mapRow(flat);
450
- },
451
- async remove(id) {
452
- const { error } = await coreClient().from(ac.table).delete().eq("id", id);
453
- if (error) throw error;
454
- },
455
- async removeMany(ids) {
456
- if (!ids.length) return;
457
- const { error } = await coreClient().from(ac.table).delete().in("id", ids);
458
- if (error) throw error;
459
- }
460
- };
461
- }
462
-
463
- // src/data/cached.ts
464
- function withCache(provider, options) {
465
- function prefix() {
466
- return `${options.tenantId() ?? "_"}:${options.table}`;
467
- }
468
- return {
469
- async list(query) {
470
- const key = `${prefix()}:${stableKey(query)}`;
471
- const cached = globalCache.get(key);
472
- if (cached) return cached;
473
- const result = await provider.list(query);
474
- globalCache.set(key, result, options.ttl);
475
- return result;
476
- },
477
- async create(data) {
478
- const result = await provider.create(data);
479
- globalCache.invalidate(prefix());
480
- return result;
481
- },
482
- // Forwarded, not dropped: an optional method that disappears behind the
483
- // wrapper silently downgrades every bulk write to a loop of single writes,
484
- // and nothing upstream can tell — `createMany` was doing exactly that.
485
- ...provider.createMany && {
486
- async createMany(rows, options2) {
487
- const result = await provider.createMany(rows, options2);
488
- globalCache.invalidate(prefix());
489
- return result;
490
- }
491
- },
492
- async update(id, data) {
493
- const result = await provider.update(id, data);
494
- globalCache.invalidate(prefix());
495
- return result;
496
- },
497
- async remove(id) {
498
- await provider.remove(id);
499
- globalCache.invalidate(prefix());
500
- },
501
- ...provider.removeMany && {
502
- async removeMany(ids) {
503
- await provider.removeMany(ids);
504
- globalCache.invalidate(prefix());
505
- }
506
- }
507
- };
508
- }
509
-
510
- // src/fields/store.ts
511
- var FIELDS_TABLE = "plg_entity_fields";
512
- var CustomFieldsError = class extends Error {
513
- constructor(reference, issues) {
514
- super(`${reference} custom fields: ${issues.map((i) => i.message).join("; ")}`);
515
- this.name = "CustomFieldsError";
516
- this.issues = issues;
517
- }
518
- };
519
- function readOptions(raw) {
520
- if (!Array.isArray(raw)) return void 0;
521
- const options = raw.map((entry) => {
522
- if (typeof entry === "string") return { value: entry };
523
- const record = entry;
524
- return typeof record?.value === "string" ? { value: record.value, label: typeof record.label === "string" ? record.label : void 0 } : null;
525
- }).filter((option) => option !== null);
526
- return options.length ? options : void 0;
527
- }
528
- function rowToField(row) {
529
- const field = { type: row.type };
530
- if (row.label) field.label = row.label;
531
- if (row.help) field.help = row.help;
532
- if (row.required) field.required = true;
533
- if (row.default_value !== null && row.default_value !== void 0) field.default = row.default_value;
534
- const options = readOptions(row.options);
535
- if (options) field.options = options;
536
- if (row.validation && Object.keys(row.validation).length) {
537
- field.validation = row.validation;
538
- }
539
- return field;
540
- }
541
- var EMPTY = { reference: "", fields: {}, order: [] };
542
- var cache2 = /* @__PURE__ */ new Map();
543
- function clearCustomFieldsCache() {
544
- cache2.clear();
545
- }
546
- async function load(reference, tenantId) {
547
- const declared = declaredCustomFields(reference);
548
- const supabase = getSupabaseClientOptional();
549
- if (!supabase) return { reference, fields: { ...declared }, order: Object.keys(declared) };
550
- const { data, error } = await supabase.from(FIELDS_TABLE).select("reference,key,type,label,help,required,default_value,validation,options,position,origin").eq("reference", reference).is("archived_at", null).order("position", { ascending: true });
551
- if (error) return { reference, fields: { ...declared }, order: Object.keys(declared) };
552
- const fields = {};
553
- const order = [];
554
- for (const row of data ?? []) {
555
- if (fields[row.key]) continue;
556
- fields[row.key] = rowToField(row);
557
- order.push(row.key);
558
- }
559
- for (const [key, field] of Object.entries(declared)) {
560
- if (!fields[key]) order.push(key);
561
- fields[key] = field;
562
- }
563
- return { reference, fields, order };
564
- }
565
- function resolveCustomFields(reference, tenantId) {
566
- const tenant = tenantId ?? getActiveTenantId();
567
- if (!tenant) return Promise.resolve({ ...EMPTY, reference, fields: { ...declaredCustomFields(reference) } });
568
- const cacheKey2 = `${tenant}:${reference}`;
569
- let pending = cache2.get(cacheKey2);
570
- if (!pending) {
571
- pending = load(reference).catch((err) => {
572
- cache2.delete(cacheKey2);
573
- throw err;
574
- });
575
- cache2.set(cacheKey2, pending);
576
- }
577
- return pending;
578
- }
579
- async function validateCustomFields(reference, values, tenantId) {
580
- const { fields } = await resolveCustomFields(reference, tenantId);
581
- const issues = validateEntityRecordData({ fields }, values);
582
- if (issues.length) throw new CustomFieldsError(reference, issues);
583
- }
584
- function mergeCustomFields(current, patch) {
585
- const merged = { ...current ?? {} };
586
- for (const [key, value] of Object.entries(patch)) {
587
- if (value === null) delete merged[key];
588
- else merged[key] = value;
589
- }
590
- return merged;
591
- }
592
-
593
- // src/data/custom-fields.ts
594
- var KEY = "customFields";
595
- function patchOf(data) {
596
- const value = data?.[KEY];
597
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
598
- }
599
- function withCustomFields(provider, reference) {
600
- return {
601
- ...provider,
602
- async create(data) {
603
- const patch = patchOf(data);
604
- if (!patch) return provider.create(data);
605
- await validateCustomFields(reference, patch);
606
- return provider.create(data);
607
- },
608
- async update(id, data) {
609
- const patch = patchOf(data);
610
- if (!patch) return provider.update(id, data);
611
- await validateCustomFields(reference, patch);
612
- const { data: rows } = await provider.list({
613
- filters: { id },
614
- pageSize: 1,
615
- countMode: "none"
616
- });
617
- const current = rows[0]?.[KEY];
618
- const merged = mergeCustomFields(current, patch);
619
- return provider.update(id, { ...data, [KEY]: merged });
620
- }
621
- };
622
- }
623
-
624
- // src/data/resolve.ts
625
- function resolveDataProvider(entityDef, mockData, options = {}) {
626
- const backend = options.backend;
627
- if (backend?.provider === "mock") {
628
- return createMockProvider(entityDef, mockData);
629
- }
630
- if (backend?.provider === "fayz-api") {
631
- const table = entityDef.data?.table ?? entityDef.name;
632
- return createFayzApiProvider(table, {
633
- ...options.fayzApi,
634
- baseUrl: options.fayzApi?.baseUrl ?? backend.url,
635
- projectId: options.fayzApi?.projectId ?? backend.projectRef,
636
- entityKey: entityDef.name ?? table,
637
- table,
638
- schema: options.fayzApi?.schema ?? entityDef.data?.schema,
639
- idColumn: options.fayzApi?.idColumn ?? entityDef.data?.columnMap?.id,
640
- searchColumns: options.fayzApi?.searchColumns ?? entityDef.data?.searchColumns ?? entityDef.fields.filter((field) => field.searchable).map((field) => field.key),
641
- tenantIdColumn: entityDef.data?.tenantScoped === false ? false : options.fayzApi?.tenantIdColumn ?? entityDef.data?.tenantIdColumn,
642
- tenantId: options.fayzApi?.tenantId ?? (() => getActiveTenantId())
643
- });
644
- }
645
- if (backend?.provider === "custom") {
646
- const adapterId = backend.adapterId;
647
- const factory = adapterId ? options.customProviders?.[adapterId] : void 0;
648
- if (!adapterId || !factory) {
649
- throw new Error(`[@fayz-ai/core] Custom data provider "${adapterId ?? "unknown"}" is not registered.`);
650
- }
651
- return factory(entityDef, mockData);
652
- }
653
- const client = getSupabaseClientOptional();
654
- if (client && entityDef.data?.table) {
655
- const tenantId = () => getActiveTenantId();
656
- const cacheOptions = {
657
- table: entityDef.data.archetypeKind ? `${entityDef.data.table}#${entityDef.data.archetypeKind}` : entityDef.data.table,
658
- tenantId,
659
- ttl: entityDef.data.cacheTTL
660
- };
661
- const cached = (p) => options.cache === false ? p : withCache(p, cacheOptions);
662
- if (entityDef.data.archetype && entityDef.data.archetypeKind && !entityDef.data.schema) {
663
- return cached(withCustomFields(createArchetypeProvider({
664
- archetype: entityDef.data.archetype,
665
- archetypeKind: entityDef.data.archetypeKind,
666
- projectTable: entityDef.data.table,
667
- tenantId,
668
- searchColumns: entityDef.data.searchColumns ?? entityDef.fields.filter((field) => field.searchable).map((field) => field.key)
669
- }), entityDef.data.archetype));
670
- }
671
- return cached(createSupabaseProvider(entityDef.data.table, {
672
- schema: entityDef.data.schema,
673
- tenantId: entityDef.data.tenantScoped === false ? void 0 : tenantId,
674
- tenantIdColumn: entityDef.data.tenantIdColumn,
675
- searchColumns: entityDef.data.searchColumns ?? entityDef.fields.filter((field) => field.searchable).map((field) => field.key),
676
- selectColumns: entityDef.data.selectColumns,
677
- columnMap: entityDef.data.columnMap,
678
- filters: entityDef.data.filters,
679
- defaults: entityDef.data.defaults
680
- }));
681
- }
682
- if (entityDef.data?.table) {
683
- console.warn(
684
- `[fayz/core] resolveDataProvider: falling back to mock for "${entityDef.name}" (table: ${entityDef.data.table}). Supabase client ${client ? "available" : "NOT initialized"}.`
685
- );
686
- }
687
- return createMockProvider(entityDef, mockData);
688
- }
689
-
690
- // src/data/bulk.ts
691
- async function bulkCreate(provider, rows, options) {
692
- if (!rows.length) return { created: 0, failed: [] };
693
- if (provider.createMany) return provider.createMany(rows, options);
694
- let created = 0;
695
- const failed = [];
696
- for (let i = 0; i < rows.length; i++) {
697
- try {
698
- await provider.create(rows[i]);
699
- created += 1;
700
- } catch (error) {
701
- failed.push({ index: i, message: error instanceof Error ? error.message : String(error) });
702
- }
703
- options?.onProgress?.(i + 1, rows.length);
704
- }
705
- return { created, failed };
706
- }
707
- async function bulkRemove(provider, ids, options) {
708
- if (!ids.length) return { removed: 0, failed: [] };
709
- const failed = [];
710
- const message = (error) => error instanceof Error ? error.message : String(error);
711
- if (provider.removeMany) {
712
- const chunkSize = options?.chunkSize ?? 100;
713
- let removed2 = 0;
714
- for (let i = 0; i < ids.length; i += chunkSize) {
715
- const chunk = ids.slice(i, i + chunkSize);
716
- try {
717
- await provider.removeMany(chunk);
718
- removed2 += chunk.length;
719
- } catch (error) {
720
- for (const id of chunk) failed.push({ id, message: message(error) });
721
- }
722
- }
723
- return { removed: removed2, failed };
724
- }
725
- let removed = 0;
726
- for (const id of ids) {
727
- try {
728
- await provider.remove(id);
729
- removed += 1;
730
- } catch (error) {
731
- failed.push({ id, message: message(error) });
732
- }
733
- }
734
- return { removed, failed };
735
- }
736
-
737
- export { CustomFieldsError, FIELDS_TABLE, bulkCreate, bulkRemove, clearCustomFieldsCache, countByTenant, createArchetypeProvider, createFayzApiProvider, createMockProvider, invalidateCount, mergeCustomFields, resolveCustomFields, resolveDataProvider, validateCustomFields, withCache };
738
- //# sourceMappingURL=chunk-UVKH6RDX.js.map
739
- //# sourceMappingURL=chunk-UVKH6RDX.js.map