@fayz-ai/core 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/render.d.ts +16 -0
- package/dist/app/render.d.ts.map +1 -0
- package/dist/blocks/index.d.ts +25 -0
- package/dist/blocks/index.d.ts.map +1 -0
- package/dist/chunk-2NTHNWEH.cjs +32 -0
- package/dist/chunk-2NTHNWEH.cjs.map +1 -0
- package/dist/chunk-5EAZOMVP.js +1609 -0
- package/dist/chunk-5EAZOMVP.js.map +1 -0
- package/dist/chunk-5TKIZVVJ.cjs +16 -0
- package/dist/chunk-5TKIZVVJ.cjs.map +1 -0
- package/dist/chunk-7RTUHSKY.js +3 -0
- package/dist/chunk-7RTUHSKY.js.map +1 -0
- package/dist/chunk-AXTWKF5X.cjs +38 -0
- package/dist/chunk-AXTWKF5X.cjs.map +1 -0
- package/dist/chunk-EGWWQ7AT.cjs +355 -0
- package/dist/chunk-EGWWQ7AT.cjs.map +1 -0
- package/dist/chunk-KAYQFSR6.js +298 -0
- package/dist/chunk-KAYQFSR6.js.map +1 -0
- package/dist/chunk-LSVBDYFQ.cjs +1639 -0
- package/dist/chunk-LSVBDYFQ.cjs.map +1 -0
- package/dist/chunk-SNQW6TRF.cjs +658 -0
- package/dist/chunk-SNQW6TRF.cjs.map +1 -0
- package/dist/chunk-WKZVSKEU.js +26 -0
- package/dist/chunk-WKZVSKEU.js.map +1 -0
- package/dist/chunk-Y2C2FBQP.js +35 -0
- package/dist/chunk-Y2C2FBQP.js.map +1 -0
- package/dist/chunk-YSBXGHAM.js +643 -0
- package/dist/chunk-YSBXGHAM.js.map +1 -0
- package/dist/data/archetype.d.ts +13 -0
- package/dist/data/archetype.d.ts.map +1 -0
- package/dist/data/cached.d.ts +19 -0
- package/dist/data/cached.d.ts.map +1 -0
- package/dist/data/fayz-api.d.ts +19 -0
- package/dist/data/fayz-api.d.ts.map +1 -0
- package/dist/data/index.cjs +32 -0
- package/dist/data/index.cjs.map +1 -0
- package/dist/data/index.d.ts +9 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +3 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/mock.d.ts +7 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/resolve.d.ts +22 -0
- package/dist/data/resolve.d.ts.map +1 -0
- package/dist/data/supabase.d.ts +18 -0
- package/dist/data/supabase.d.ts.map +1 -0
- package/dist/data/types.d.ts +23 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/entity/index.cjs +28 -0
- package/dist/entity/index.cjs.map +1 -0
- package/dist/entity/index.d.ts +3 -0
- package/dist/entity/index.d.ts.map +1 -0
- package/dist/entity/index.js +3 -0
- package/dist/entity/index.js.map +1 -0
- package/dist/entity/registry.d.ts +20 -0
- package/dist/entity/registry.d.ts.map +1 -0
- package/dist/events/index.d.ts +16 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/i18n/index.cjs +44 -0
- package/dist/i18n/index.cjs.map +1 -0
- package/dist/i18n/index.d.ts +21 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +3 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/shell-translations.d.ts +2 -0
- package/dist/i18n/shell-translations.d.ts.map +1 -0
- package/dist/index.cjs +911 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +596 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cache.d.ts +21 -0
- package/dist/lib/cache.d.ts.map +1 -0
- package/dist/lib/csv.d.ts +14 -0
- package/dist/lib/csv.d.ts.map +1 -0
- package/dist/lib/format.d.ts +7 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/manifest/index.d.ts +80 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/plugin/createSafeDataProvider.d.ts +14 -0
- package/dist/plugin/createSafeDataProvider.d.ts.map +1 -0
- package/dist/plugin/index.cjs +32 -0
- package/dist/plugin/index.cjs.map +1 -0
- package/dist/plugin/index.d.ts +2 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +3 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/runtime.d.ts +31 -0
- package/dist/plugin/runtime.d.ts.map +1 -0
- package/dist/registry/index.d.ts +88 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/router/index.cjs +16 -0
- package/dist/router/index.cjs.map +1 -0
- package/dist/router/index.d.ts +8 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +3 -0
- package/dist/router/index.js.map +1 -0
- package/dist/runtime/index.cjs +16 -0
- package/dist/runtime/index.cjs.map +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/oauth.d.ts +96 -0
- package/dist/runtime/oauth.d.ts.map +1 -0
- package/dist/tenant/index.d.ts +3 -0
- package/dist/tenant/index.d.ts.map +1 -0
- package/dist/types/auth.d.ts +27 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/billing.d.ts +17 -0
- package/dist/types/billing.d.ts.map +1 -0
- package/dist/types/crud.d.ts +107 -0
- package/dist/types/crud.d.ts.map +1 -0
- package/dist/types/entities.d.ts +134 -0
- package/dist/types/entities.d.ts.map +1 -0
- package/dist/types/i18n.d.ts +8 -0
- package/dist/types/i18n.d.ts.map +1 -0
- package/dist/types/index.cjs +4 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/org.d.ts +96 -0
- package/dist/types/org.d.ts.map +1 -0
- package/dist/types/permissions.d.ts +25 -0
- package/dist/types/permissions.d.ts.map +1 -0
- package/dist/types/plugins.d.ts +314 -0
- package/dist/types/plugins.d.ts.map +1 -0
- package/dist/types/theme.d.ts +16 -0
- package/dist/types/theme.d.ts.map +1 -0
- package/package.json +98 -0
- package/src/app/render.tsx +69 -0
- package/src/blocks/index.tsx +68 -0
- package/src/data/archetype.ts +210 -0
- package/src/data/cached.ts +55 -0
- package/src/data/fayz-api.ts +200 -0
- package/src/data/index.ts +8 -0
- package/src/data/mock.ts +84 -0
- package/src/data/resolve.ts +108 -0
- package/src/data/supabase.ts +169 -0
- package/src/data/types.ts +20 -0
- package/src/entity/index.ts +2 -0
- package/src/entity/registry.ts +42 -0
- package/src/events/index.tsx +82 -0
- package/src/i18n/index.ts +250 -0
- package/src/i18n/shell-translations.ts +1391 -0
- package/src/index.ts +132 -0
- package/src/lib/cache.ts +118 -0
- package/src/lib/csv.ts +54 -0
- package/src/lib/format.ts +58 -0
- package/src/manifest/app-manifest.schema.json +103 -0
- package/src/manifest/index.ts +394 -0
- package/src/plugin/createSafeDataProvider.ts +30 -0
- package/src/plugin/index.ts +1 -0
- package/src/plugin/runtime.ts +313 -0
- package/src/registry/index.ts +163 -0
- package/src/router/index.ts +29 -0
- package/src/runtime/index.ts +15 -0
- package/src/runtime/oauth.ts +219 -0
- package/src/tenant/index.ts +18 -0
- package/src/types/auth.ts +26 -0
- package/src/types/billing.ts +17 -0
- package/src/types/crud.ts +108 -0
- package/src/types/entities.ts +142 -0
- package/src/types/i18n.ts +7 -0
- package/src/types/index.ts +10 -0
- package/src/types/org.ts +94 -0
- package/src/types/permissions.ts +32 -0
- package/src/types/plugins.ts +363 -0
- package/src/types/theme.ts +17 -0
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
// src/data/supabase.ts
|
|
2
|
+
function camelToSnake(str) {
|
|
3
|
+
return str.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase());
|
|
4
|
+
}
|
|
5
|
+
function snakeToCamel(str) {
|
|
6
|
+
return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
7
|
+
}
|
|
8
|
+
function mapRow(row, columnMap) {
|
|
9
|
+
const result = {};
|
|
10
|
+
for (const [key, value] of Object.entries(row)) {
|
|
11
|
+
const camelKey = columnMap?.[key] ?? snakeToCamel(key);
|
|
12
|
+
result[camelKey] = value;
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
function mapEntity(entity, columnMap) {
|
|
17
|
+
const reverseMap = {};
|
|
18
|
+
if (columnMap) {
|
|
19
|
+
for (const [snake, camel] of Object.entries(columnMap)) {
|
|
20
|
+
reverseMap[camel] = snake;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const result = {};
|
|
24
|
+
for (const [key, value] of Object.entries(entity)) {
|
|
25
|
+
if (key === "id" || key === "createdAt" || key === "updatedAt" || key === "tenantId") continue;
|
|
26
|
+
const snakeKey = reverseMap[key] ?? camelToSnake(key);
|
|
27
|
+
result[snakeKey] = value;
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
var _globalSupabaseClient = null;
|
|
32
|
+
function setGlobalSupabaseClient(client) {
|
|
33
|
+
_globalSupabaseClient = client;
|
|
34
|
+
}
|
|
35
|
+
function getSupabaseClientOptional() {
|
|
36
|
+
return _globalSupabaseClient;
|
|
37
|
+
}
|
|
38
|
+
function createSupabaseProvider(table, config) {
|
|
39
|
+
const schema = config?.schema ?? "public";
|
|
40
|
+
const tenantIdCol = config?.tenantIdColumn ?? "tenant_id";
|
|
41
|
+
const searchCols = config?.searchColumns ?? [];
|
|
42
|
+
const selectCols = config?.selectColumns ?? "*";
|
|
43
|
+
const columnMap = config?.columnMap;
|
|
44
|
+
function resolveTenantId2() {
|
|
45
|
+
if (!config?.tenantId) return void 0;
|
|
46
|
+
return typeof config.tenantId === "function" ? config.tenantId() : config.tenantId;
|
|
47
|
+
}
|
|
48
|
+
function getClient() {
|
|
49
|
+
const supabase = config?.supabaseClient ?? _globalSupabaseClient;
|
|
50
|
+
if (!supabase) throw new Error(`[@fayz-ai/core] Supabase client not available. Call createFayzApp with a provider first.`);
|
|
51
|
+
return schema === "public" ? supabase : supabase.schema(schema);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
async list(query) {
|
|
55
|
+
let q = getClient().from(table).select(selectCols, { count: "exact" });
|
|
56
|
+
const tenantId = resolveTenantId2();
|
|
57
|
+
if (tenantId) q = q.eq(tenantIdCol, tenantId);
|
|
58
|
+
if (config?.filters) {
|
|
59
|
+
for (const [col, val] of Object.entries(config.filters)) {
|
|
60
|
+
q = q.eq(col, val);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (query.filters) {
|
|
64
|
+
for (const [key, val] of Object.entries(query.filters)) {
|
|
65
|
+
if (val == null || val === "") continue;
|
|
66
|
+
const col = columnMap?.[key] ?? camelToSnake(key);
|
|
67
|
+
q = q.eq(col, val);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (query.search && searchCols.length > 0) {
|
|
71
|
+
const term = `%${query.search}%`;
|
|
72
|
+
const orClause = searchCols.map((col) => `${camelToSnake(col)}.ilike.${term}`).join(",");
|
|
73
|
+
q = q.or(orClause);
|
|
74
|
+
}
|
|
75
|
+
if (query.sortBy) {
|
|
76
|
+
q = q.order(camelToSnake(query.sortBy), { ascending: query.sortDir !== "desc" });
|
|
77
|
+
} else {
|
|
78
|
+
q = q.order("created_at", { ascending: false });
|
|
79
|
+
}
|
|
80
|
+
const page = query.page ?? 1;
|
|
81
|
+
const pageSize = query.pageSize ?? 50;
|
|
82
|
+
const from = (page - 1) * pageSize;
|
|
83
|
+
q = q.range(from, from + pageSize - 1);
|
|
84
|
+
const { data, error, count } = await q;
|
|
85
|
+
if (error) throw error;
|
|
86
|
+
return {
|
|
87
|
+
data: (data ?? []).map((row) => mapRow(row, columnMap)),
|
|
88
|
+
total: count ?? 0
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
async create(data) {
|
|
92
|
+
const row = mapEntity(data, columnMap);
|
|
93
|
+
const tenantId = resolveTenantId2();
|
|
94
|
+
if (tenantId) row[tenantIdCol] = tenantId;
|
|
95
|
+
if (config?.defaults) {
|
|
96
|
+
for (const [col, val] of Object.entries(config.defaults)) {
|
|
97
|
+
if (row[col] === void 0) row[col] = val;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const { data: created, error } = await getClient().from(table).insert(row).select().single();
|
|
101
|
+
if (error) throw error;
|
|
102
|
+
return mapRow(created, columnMap);
|
|
103
|
+
},
|
|
104
|
+
async update(id, data) {
|
|
105
|
+
const row = mapEntity(data, columnMap);
|
|
106
|
+
const { data: updated, error } = await getClient().from(table).update(row).eq("id", id).select().single();
|
|
107
|
+
if (error) throw error;
|
|
108
|
+
return mapRow(updated, columnMap);
|
|
109
|
+
},
|
|
110
|
+
async remove(id) {
|
|
111
|
+
const { error } = await getClient().from(table).delete().eq("id", id);
|
|
112
|
+
if (error) throw error;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/data/fayz-api.ts
|
|
118
|
+
function resolveTenantId(config) {
|
|
119
|
+
const tenantId = config?.tenantId;
|
|
120
|
+
return typeof tenantId === "function" ? tenantId() : tenantId;
|
|
121
|
+
}
|
|
122
|
+
async function resolveRuntimeToken(config) {
|
|
123
|
+
const runtimeToken = config?.runtimeToken;
|
|
124
|
+
return typeof runtimeToken === "function" ? runtimeToken() : runtimeToken;
|
|
125
|
+
}
|
|
126
|
+
function trimTrailingSlash(value) {
|
|
127
|
+
return value.replace(/\/+$/, "");
|
|
128
|
+
}
|
|
129
|
+
function appendQuery(url, query, tenantId, tenantIdColumn = "tenant_id", searchColumns = []) {
|
|
130
|
+
const filters = normalizeFilters(query.filters);
|
|
131
|
+
if (query.search && searchColumns[0]) {
|
|
132
|
+
filters.push({ column: searchColumns[0], operator: "ilike", value: `%${query.search}%` });
|
|
133
|
+
}
|
|
134
|
+
if (query.sortBy) url.searchParams.set("sortColumn", query.sortBy);
|
|
135
|
+
if (query.sortDir) url.searchParams.set("sortDirection", query.sortDir);
|
|
136
|
+
if (query.page != null) url.searchParams.set("page", String(query.page));
|
|
137
|
+
if (query.pageSize != null) url.searchParams.set("limit", String(query.pageSize));
|
|
138
|
+
if (tenantId) filters.push({ column: tenantIdColumn, operator: "eq", value: tenantId });
|
|
139
|
+
if (filters.length > 0) url.searchParams.set("filters", JSON.stringify(filters));
|
|
140
|
+
}
|
|
141
|
+
async function parseResponse(response) {
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
const text = await response.text().catch(() => "");
|
|
144
|
+
throw new Error(`[@fayz-ai/core] Fayz API request failed (${response.status}): ${text || response.statusText}`);
|
|
145
|
+
}
|
|
146
|
+
return response.json();
|
|
147
|
+
}
|
|
148
|
+
function isFilterOperator(value) {
|
|
149
|
+
return typeof value === "string" && [
|
|
150
|
+
"eq",
|
|
151
|
+
"neq",
|
|
152
|
+
"gt",
|
|
153
|
+
"lt",
|
|
154
|
+
"gte",
|
|
155
|
+
"lte",
|
|
156
|
+
"like",
|
|
157
|
+
"ilike",
|
|
158
|
+
"is_null",
|
|
159
|
+
"is_not_null"
|
|
160
|
+
].includes(value);
|
|
161
|
+
}
|
|
162
|
+
function isRecord(value) {
|
|
163
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
164
|
+
}
|
|
165
|
+
function normalizeFilters(filters) {
|
|
166
|
+
if (!filters) return [];
|
|
167
|
+
return Object.entries(filters).map(([column, raw]) => {
|
|
168
|
+
if (isRecord(raw) && isFilterOperator(raw.operator)) {
|
|
169
|
+
return { column, operator: raw.operator, value: raw.value };
|
|
170
|
+
}
|
|
171
|
+
return { column, operator: "eq", value: raw };
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function createFayzApiProvider(entityOrTable, config = {}) {
|
|
175
|
+
const fetcher = config.fetcher ?? fetch;
|
|
176
|
+
const baseUrl = trimTrailingSlash(config.baseUrl ?? "");
|
|
177
|
+
const projectId = config.projectId;
|
|
178
|
+
const table = config.table ?? entityOrTable;
|
|
179
|
+
const idColumn = config.idColumn ?? "id";
|
|
180
|
+
async function headers(runtimeToken) {
|
|
181
|
+
return {
|
|
182
|
+
"Content-Type": "application/json",
|
|
183
|
+
...config.headers ? await config.headers() : {},
|
|
184
|
+
...runtimeToken ? { Authorization: `Bearer ${runtimeToken}` } : {}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function tableRowsUrl(runtime = false) {
|
|
188
|
+
const prefix = baseUrl || (typeof window !== "undefined" ? window.location.origin : "http://localhost");
|
|
189
|
+
const encodedProjectId = encodeURIComponent(projectId ?? "current");
|
|
190
|
+
const encodedTable = encodeURIComponent(table);
|
|
191
|
+
const route = runtime ? `/api/v1/runtime/projects/${encodedProjectId}/database/tables/${encodedTable}/rows` : `/api/projects/${encodedProjectId}/database/tables/${encodedTable}/rows`;
|
|
192
|
+
const url = new URL(route, prefix);
|
|
193
|
+
if (config.schema) url.searchParams.set("schema", config.schema);
|
|
194
|
+
return url;
|
|
195
|
+
}
|
|
196
|
+
function withTenant(data, runtime = false) {
|
|
197
|
+
if (runtime) return data;
|
|
198
|
+
const tenantId = resolveTenantId(config);
|
|
199
|
+
if (!tenantId || config.tenantIdColumn === false) return data;
|
|
200
|
+
return { ...data, [config.tenantIdColumn ?? "tenant_id"]: tenantId };
|
|
201
|
+
}
|
|
202
|
+
function primaryKeys(id) {
|
|
203
|
+
return { [idColumn]: id };
|
|
204
|
+
}
|
|
205
|
+
async function listRows(query) {
|
|
206
|
+
const runtimeToken = await resolveRuntimeToken(config);
|
|
207
|
+
const runtime = Boolean(runtimeToken);
|
|
208
|
+
const url = tableRowsUrl(runtime);
|
|
209
|
+
appendQuery(
|
|
210
|
+
url,
|
|
211
|
+
query,
|
|
212
|
+
runtime || config.tenantIdColumn === false ? void 0 : resolveTenantId(config),
|
|
213
|
+
config.tenantIdColumn || void 0,
|
|
214
|
+
config.searchColumns
|
|
215
|
+
);
|
|
216
|
+
const result = await parseResponse(await fetcher(url, { headers: await headers(runtimeToken) }));
|
|
217
|
+
return { data: result.rows, total: result.total };
|
|
218
|
+
}
|
|
219
|
+
async function assertTenantScopedRowExists(id, runtime = false) {
|
|
220
|
+
if (runtime) return;
|
|
221
|
+
const tenantId = config.tenantIdColumn === false ? void 0 : resolveTenantId(config);
|
|
222
|
+
if (!tenantId) return;
|
|
223
|
+
const result = await listRows({ filters: { [idColumn]: id }, page: 1, pageSize: 1 });
|
|
224
|
+
if (result.data.length === 0) {
|
|
225
|
+
throw new Error(`[@fayz-ai/core] Row "${id}" was not found for the active tenant.`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
async list(query) {
|
|
230
|
+
return listRows(query);
|
|
231
|
+
},
|
|
232
|
+
async create(data) {
|
|
233
|
+
const runtimeToken = await resolveRuntimeToken(config);
|
|
234
|
+
const runtime = Boolean(runtimeToken);
|
|
235
|
+
return parseResponse(await fetcher(tableRowsUrl(runtime), {
|
|
236
|
+
method: "POST",
|
|
237
|
+
headers: await headers(runtimeToken),
|
|
238
|
+
body: JSON.stringify(withTenant(data, runtime))
|
|
239
|
+
}));
|
|
240
|
+
},
|
|
241
|
+
async update(id, data) {
|
|
242
|
+
const runtimeToken = await resolveRuntimeToken(config);
|
|
243
|
+
const runtime = Boolean(runtimeToken);
|
|
244
|
+
await assertTenantScopedRowExists(id, runtime);
|
|
245
|
+
return parseResponse(await fetcher(tableRowsUrl(runtime), {
|
|
246
|
+
method: "PATCH",
|
|
247
|
+
headers: await headers(runtimeToken),
|
|
248
|
+
body: JSON.stringify({ primaryKeys: primaryKeys(id), data })
|
|
249
|
+
}));
|
|
250
|
+
},
|
|
251
|
+
async remove(id) {
|
|
252
|
+
const runtimeToken = await resolveRuntimeToken(config);
|
|
253
|
+
const runtime = Boolean(runtimeToken);
|
|
254
|
+
await assertTenantScopedRowExists(id, runtime);
|
|
255
|
+
await parseResponse(await fetcher(tableRowsUrl(runtime), {
|
|
256
|
+
method: "DELETE",
|
|
257
|
+
headers: await headers(runtimeToken),
|
|
258
|
+
body: JSON.stringify({ rows: [primaryKeys(id)] })
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/data/mock.ts
|
|
265
|
+
function createMockProvider(entityDefOrSearchKeys, initialData = []) {
|
|
266
|
+
let items = [...initialData];
|
|
267
|
+
const searchableKeys = Array.isArray(entityDefOrSearchKeys) ? entityDefOrSearchKeys : entityDefOrSearchKeys.fields.filter((f) => f.searchable).map((f) => f.key);
|
|
268
|
+
const defaultSort = Array.isArray(entityDefOrSearchKeys) ? void 0 : entityDefOrSearchKeys.defaultSort;
|
|
269
|
+
const defaultSortDir = Array.isArray(entityDefOrSearchKeys) ? "asc" : entityDefOrSearchKeys.defaultSortDir ?? "asc";
|
|
270
|
+
return {
|
|
271
|
+
async list(query) {
|
|
272
|
+
let filtered = [...items];
|
|
273
|
+
if (query.search && searchableKeys.length > 0) {
|
|
274
|
+
const term = query.search.toLowerCase();
|
|
275
|
+
filtered = filtered.filter(
|
|
276
|
+
(item) => searchableKeys.some((key) => {
|
|
277
|
+
const val = item[key];
|
|
278
|
+
return typeof val === "string" && val.toLowerCase().includes(term);
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
if (query.filters) {
|
|
283
|
+
for (const [key, val] of Object.entries(query.filters)) {
|
|
284
|
+
if (val == null || val === "") continue;
|
|
285
|
+
filtered = filtered.filter((item) => item[key] === val);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const sortBy = query.sortBy ?? defaultSort;
|
|
289
|
+
const sortDir = query.sortDir ?? defaultSortDir;
|
|
290
|
+
if (sortBy) {
|
|
291
|
+
filtered.sort((a, b) => {
|
|
292
|
+
const av = a[sortBy] ?? "";
|
|
293
|
+
const bv = b[sortBy] ?? "";
|
|
294
|
+
const cmp = typeof av === "number" && typeof bv === "number" ? av - bv : String(av).localeCompare(String(bv));
|
|
295
|
+
return sortDir === "desc" ? -cmp : cmp;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
const total = filtered.length;
|
|
299
|
+
const page = query.page ?? 1;
|
|
300
|
+
const pageSize = query.pageSize ?? 50;
|
|
301
|
+
const start = (page - 1) * pageSize;
|
|
302
|
+
const data = filtered.slice(start, start + pageSize);
|
|
303
|
+
return { data, total };
|
|
304
|
+
},
|
|
305
|
+
async create(data) {
|
|
306
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
307
|
+
const item = {
|
|
308
|
+
...data,
|
|
309
|
+
id: crypto.randomUUID(),
|
|
310
|
+
tenantId: "mock-tenant",
|
|
311
|
+
createdAt: now,
|
|
312
|
+
updatedAt: now
|
|
313
|
+
};
|
|
314
|
+
items.unshift(item);
|
|
315
|
+
return item;
|
|
316
|
+
},
|
|
317
|
+
async update(id, data) {
|
|
318
|
+
const idx = items.findIndex((i) => i.id === id);
|
|
319
|
+
if (idx === -1) throw new Error(`[@fayz-ai/core] Mock provider: item not found: ${id}`);
|
|
320
|
+
items[idx] = { ...items[idx], ...data, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
321
|
+
return items[idx];
|
|
322
|
+
},
|
|
323
|
+
async remove(id) {
|
|
324
|
+
items = items.filter((i) => i.id !== id);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/data/archetype.ts
|
|
330
|
+
var HAS_KIND = /* @__PURE__ */ new Set(["person", "category", "order", "transaction", "schedule", "location"]);
|
|
331
|
+
var ARCHETYPE_CONFIG = {
|
|
332
|
+
person: { table: "persons", fkColumn: "person_id" },
|
|
333
|
+
category: { table: "categories", fkColumn: "category_id" },
|
|
334
|
+
product: { table: "products", fkColumn: "product_id" },
|
|
335
|
+
service: { table: "services", fkColumn: "service_id" },
|
|
336
|
+
location: { table: "locations", fkColumn: "location_id" },
|
|
337
|
+
order: { table: "orders", fkColumn: "order_id" },
|
|
338
|
+
transaction: { table: "transactions", fkColumn: "transaction_id" },
|
|
339
|
+
schedule: { table: "schedules", fkColumn: "schedule_id" }
|
|
340
|
+
};
|
|
341
|
+
var ARCHETYPE_COLUMNS = {
|
|
342
|
+
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"]),
|
|
343
|
+
category: /* @__PURE__ */ new Set(["name", "slug", "parent_id", "icon", "color", "sort_order", "is_active", "metadata"]),
|
|
344
|
+
product: /* @__PURE__ */ new Set(["name", "description", "sku", "price", "cost", "currency", "unit", "image_url", "stock", "min_stock", "status", "is_active", "tags", "metadata", "category_id"]),
|
|
345
|
+
service: /* @__PURE__ */ new Set(["name", "description", "price", "cost", "currency", "duration_minutes", "image_url", "status", "is_active", "tags", "metadata", "category_id"]),
|
|
346
|
+
location: /* @__PURE__ */ new Set(["name", "email", "phone", "address", "city", "state", "country", "postal_code", "is_headquarters", "is_active", "tags", "notes", "metadata"]),
|
|
347
|
+
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"]),
|
|
348
|
+
transaction: /* @__PURE__ */ new Set(["order_id", "party_id", "amount", "currency", "payment_method", "reference", "status", "transacted_at", "notes", "metadata"]),
|
|
349
|
+
schedule: /* @__PURE__ */ new Set(["assignee_id", "location_id", "day_of_week", "specific_date", "starts_at", "ends_at", "is_active", "metadata"])
|
|
350
|
+
};
|
|
351
|
+
function camelToSnake2(str) {
|
|
352
|
+
return str.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase());
|
|
353
|
+
}
|
|
354
|
+
function snakeToCamel2(str) {
|
|
355
|
+
return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
356
|
+
}
|
|
357
|
+
function mapRow2(row) {
|
|
358
|
+
const result = {};
|
|
359
|
+
for (const [key, value] of Object.entries(row)) {
|
|
360
|
+
result[snakeToCamel2(key)] = value;
|
|
361
|
+
}
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
function createArchetypeProvider(config) {
|
|
365
|
+
const ac = ARCHETYPE_CONFIG[config.archetype];
|
|
366
|
+
const archetypeColumns = ARCHETYPE_COLUMNS[config.archetype];
|
|
367
|
+
const { fkColumn } = ac;
|
|
368
|
+
function getClient() {
|
|
369
|
+
const supabase = getSupabaseClientOptional();
|
|
370
|
+
if (!supabase) throw new Error("Supabase client not available");
|
|
371
|
+
return supabase;
|
|
372
|
+
}
|
|
373
|
+
function coreClient() {
|
|
374
|
+
return getClient().schema("saas_core");
|
|
375
|
+
}
|
|
376
|
+
function splitFields(data) {
|
|
377
|
+
const archetypeData = {};
|
|
378
|
+
const projectData = {};
|
|
379
|
+
for (const [key, value] of Object.entries(data)) {
|
|
380
|
+
if (["id", "createdAt", "updatedAt", "tenantId", fkColumn].includes(key)) continue;
|
|
381
|
+
const snakeKey = camelToSnake2(key);
|
|
382
|
+
if (archetypeColumns.has(snakeKey)) {
|
|
383
|
+
archetypeData[snakeKey] = value;
|
|
384
|
+
} else {
|
|
385
|
+
projectData[snakeKey] = value;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return { archetypeData, projectData };
|
|
389
|
+
}
|
|
390
|
+
const VIEW_MAP = {
|
|
391
|
+
clients: "v_clients",
|
|
392
|
+
staff_members: "v_staff"
|
|
393
|
+
};
|
|
394
|
+
const viewName = VIEW_MAP[config.projectTable] ?? `v_${config.projectTable}`;
|
|
395
|
+
return {
|
|
396
|
+
async list(query) {
|
|
397
|
+
const tenantId = config.tenantId();
|
|
398
|
+
if (!tenantId) return { data: [], total: 0 };
|
|
399
|
+
let q = getClient().from(viewName).select("*", { count: "exact" }).eq("tenant_id", tenantId);
|
|
400
|
+
if (query.search && config.searchColumns && config.searchColumns.length > 0) {
|
|
401
|
+
const term = `%${query.search}%`;
|
|
402
|
+
const orClauses = config.searchColumns.map((col) => camelToSnake2(col)).map((col) => `${col}.ilike.${term}`);
|
|
403
|
+
if (orClauses.length > 0) {
|
|
404
|
+
q = q.or(orClauses.join(","));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (query.sortBy) {
|
|
408
|
+
q = q.order(camelToSnake2(query.sortBy), { ascending: query.sortDir !== "desc" });
|
|
409
|
+
} else {
|
|
410
|
+
q = q.order("created_at", { ascending: false });
|
|
411
|
+
}
|
|
412
|
+
const page = query.page ?? 1;
|
|
413
|
+
const pageSize = query.pageSize ?? 50;
|
|
414
|
+
const from = (page - 1) * pageSize;
|
|
415
|
+
q = q.range(from, from + pageSize - 1);
|
|
416
|
+
const { data, error, count } = await q;
|
|
417
|
+
if (error) throw error;
|
|
418
|
+
const rows = (data ?? []).map((row) => mapRow2(row));
|
|
419
|
+
return { data: rows, total: count ?? 0 };
|
|
420
|
+
},
|
|
421
|
+
async create(data) {
|
|
422
|
+
const tenantId = config.tenantId();
|
|
423
|
+
if (!tenantId) throw new Error("No tenant selected");
|
|
424
|
+
const { archetypeData, projectData } = splitFields(data);
|
|
425
|
+
archetypeData.tenant_id = tenantId;
|
|
426
|
+
if (HAS_KIND.has(config.archetype)) {
|
|
427
|
+
archetypeData.kind = config.archetypeKind;
|
|
428
|
+
}
|
|
429
|
+
const { data: archetypeRow, error: archetypeError } = await coreClient().from(ac.table).insert(archetypeData).select().single();
|
|
430
|
+
if (archetypeError) throw archetypeError;
|
|
431
|
+
const archetypeId = archetypeRow.id;
|
|
432
|
+
projectData[fkColumn] = archetypeId;
|
|
433
|
+
projectData.tenant_id = tenantId;
|
|
434
|
+
const { error: projectError } = await getClient().from(config.projectTable).insert(projectData);
|
|
435
|
+
if (projectError) {
|
|
436
|
+
await coreClient().from(ac.table).delete().eq("id", archetypeId);
|
|
437
|
+
throw projectError;
|
|
438
|
+
}
|
|
439
|
+
return { id: archetypeId, ...archetypeRow, ...projectData };
|
|
440
|
+
},
|
|
441
|
+
async update(id, data) {
|
|
442
|
+
const { archetypeData, projectData } = splitFields(data);
|
|
443
|
+
if (Object.keys(archetypeData).length > 0) {
|
|
444
|
+
const { error } = await coreClient().from(ac.table).update(archetypeData).eq("id", id);
|
|
445
|
+
if (error) throw error;
|
|
446
|
+
}
|
|
447
|
+
if (Object.keys(projectData).length > 0) {
|
|
448
|
+
const { error } = await getClient().from(config.projectTable).update(projectData).eq(fkColumn, id);
|
|
449
|
+
if (error) throw error;
|
|
450
|
+
}
|
|
451
|
+
const { data: archetypeRow } = await coreClient().from(ac.table).select("*").eq("id", id).single();
|
|
452
|
+
const { data: projectRow } = await getClient().from(config.projectTable).select("*").eq(fkColumn, id).single();
|
|
453
|
+
const flat = { ...archetypeRow, ...projectRow, id };
|
|
454
|
+
delete flat[fkColumn];
|
|
455
|
+
return mapRow2(flat);
|
|
456
|
+
},
|
|
457
|
+
async remove(id) {
|
|
458
|
+
const { error } = await coreClient().from(ac.table).delete().eq("id", id);
|
|
459
|
+
if (error) throw error;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/lib/cache.ts
|
|
465
|
+
function sortDeep(value) {
|
|
466
|
+
if (value === null || value === void 0 || typeof value !== "object") return value;
|
|
467
|
+
if (Array.isArray(value)) return value.map(sortDeep);
|
|
468
|
+
const sorted = {};
|
|
469
|
+
for (const k of Object.keys(value).sort()) {
|
|
470
|
+
sorted[k] = sortDeep(value[k]);
|
|
471
|
+
}
|
|
472
|
+
return sorted;
|
|
473
|
+
}
|
|
474
|
+
function stableKey(...parts) {
|
|
475
|
+
return parts.map((p) => typeof p === "string" ? p : JSON.stringify(sortDeep(p))).join(":");
|
|
476
|
+
}
|
|
477
|
+
function createCacheStore(config) {
|
|
478
|
+
const defaultTTL = config?.defaultTTL ?? 6e4;
|
|
479
|
+
const maxEntries = config?.maxEntries ?? 500;
|
|
480
|
+
const entries = /* @__PURE__ */ new Map();
|
|
481
|
+
function evictLRU() {
|
|
482
|
+
if (entries.size <= maxEntries) return;
|
|
483
|
+
let oldestKey = null;
|
|
484
|
+
let oldestAccess = Infinity;
|
|
485
|
+
for (const [key, entry] of entries) {
|
|
486
|
+
if (entry.lastAccess < oldestAccess) {
|
|
487
|
+
oldestAccess = entry.lastAccess;
|
|
488
|
+
oldestKey = key;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (oldestKey) entries.delete(oldestKey);
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
get(key) {
|
|
495
|
+
const entry = entries.get(key);
|
|
496
|
+
if (!entry) return void 0;
|
|
497
|
+
if (Date.now() > entry.expiresAt) {
|
|
498
|
+
entries.delete(key);
|
|
499
|
+
return void 0;
|
|
500
|
+
}
|
|
501
|
+
entry.lastAccess = Date.now();
|
|
502
|
+
return entry.data;
|
|
503
|
+
},
|
|
504
|
+
set(key, data, ttl) {
|
|
505
|
+
const now = Date.now();
|
|
506
|
+
entries.set(key, {
|
|
507
|
+
data,
|
|
508
|
+
expiresAt: now + (ttl ?? defaultTTL),
|
|
509
|
+
lastAccess: now
|
|
510
|
+
});
|
|
511
|
+
evictLRU();
|
|
512
|
+
},
|
|
513
|
+
invalidate(prefix) {
|
|
514
|
+
for (const key of entries.keys()) {
|
|
515
|
+
if (key.startsWith(prefix)) {
|
|
516
|
+
entries.delete(key);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
clear() {
|
|
521
|
+
entries.clear();
|
|
522
|
+
},
|
|
523
|
+
size() {
|
|
524
|
+
return entries.size;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
var globalCache = createCacheStore();
|
|
529
|
+
function clearGlobalCache() {
|
|
530
|
+
globalCache.clear();
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// src/data/cached.ts
|
|
534
|
+
function withCache(provider, options) {
|
|
535
|
+
function prefix() {
|
|
536
|
+
return `${options.tenantId() ?? "_"}:${options.table}`;
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
async list(query) {
|
|
540
|
+
const key = `${prefix()}:${stableKey(query)}`;
|
|
541
|
+
const cached = globalCache.get(key);
|
|
542
|
+
if (cached) return cached;
|
|
543
|
+
const result = await provider.list(query);
|
|
544
|
+
globalCache.set(key, result, options.ttl);
|
|
545
|
+
return result;
|
|
546
|
+
},
|
|
547
|
+
async create(data) {
|
|
548
|
+
const result = await provider.create(data);
|
|
549
|
+
globalCache.invalidate(prefix());
|
|
550
|
+
return result;
|
|
551
|
+
},
|
|
552
|
+
async update(id, data) {
|
|
553
|
+
const result = await provider.update(id, data);
|
|
554
|
+
globalCache.invalidate(prefix());
|
|
555
|
+
return result;
|
|
556
|
+
},
|
|
557
|
+
async remove(id) {
|
|
558
|
+
await provider.remove(id);
|
|
559
|
+
globalCache.invalidate(prefix());
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/tenant/index.ts
|
|
565
|
+
var _activeTenantId;
|
|
566
|
+
function setActiveTenantId(id) {
|
|
567
|
+
if (id !== _activeTenantId) {
|
|
568
|
+
_activeTenantId = id;
|
|
569
|
+
clearGlobalCache();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function getActiveTenantId() {
|
|
573
|
+
return _activeTenantId;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// src/data/resolve.ts
|
|
577
|
+
function resolveDataProvider(entityDef, mockData, options = {}) {
|
|
578
|
+
const backend = options.backend;
|
|
579
|
+
if (backend?.provider === "mock") {
|
|
580
|
+
return createMockProvider(entityDef, mockData);
|
|
581
|
+
}
|
|
582
|
+
if (backend?.provider === "fayz-api") {
|
|
583
|
+
const table = entityDef.data?.table ?? entityDef.name;
|
|
584
|
+
return createFayzApiProvider(table, {
|
|
585
|
+
...options.fayzApi,
|
|
586
|
+
baseUrl: options.fayzApi?.baseUrl ?? backend.url,
|
|
587
|
+
projectId: options.fayzApi?.projectId ?? backend.projectRef,
|
|
588
|
+
entityKey: entityDef.name ?? table,
|
|
589
|
+
table,
|
|
590
|
+
schema: options.fayzApi?.schema ?? entityDef.data?.schema,
|
|
591
|
+
idColumn: options.fayzApi?.idColumn ?? entityDef.data?.columnMap?.id,
|
|
592
|
+
searchColumns: options.fayzApi?.searchColumns ?? entityDef.data?.searchColumns ?? entityDef.fields.filter((field) => field.searchable).map((field) => field.key),
|
|
593
|
+
tenantIdColumn: entityDef.data?.tenantScoped === false ? false : options.fayzApi?.tenantIdColumn ?? entityDef.data?.tenantIdColumn,
|
|
594
|
+
tenantId: options.fayzApi?.tenantId ?? (() => getActiveTenantId())
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
if (backend?.provider === "custom") {
|
|
598
|
+
const adapterId = backend.adapterId;
|
|
599
|
+
const factory = adapterId ? options.customProviders?.[adapterId] : void 0;
|
|
600
|
+
if (!adapterId || !factory) {
|
|
601
|
+
throw new Error(`[@fayz-ai/core] Custom data provider "${adapterId ?? "unknown"}" is not registered.`);
|
|
602
|
+
}
|
|
603
|
+
return factory(entityDef, mockData);
|
|
604
|
+
}
|
|
605
|
+
const client = getSupabaseClientOptional();
|
|
606
|
+
if (client && entityDef.data?.table) {
|
|
607
|
+
const tenantId = () => getActiveTenantId();
|
|
608
|
+
const cacheOptions = {
|
|
609
|
+
table: entityDef.data.table,
|
|
610
|
+
tenantId,
|
|
611
|
+
ttl: entityDef.data.cacheTTL
|
|
612
|
+
};
|
|
613
|
+
if (entityDef.data.archetype && entityDef.data.archetypeKind && !entityDef.data.schema) {
|
|
614
|
+
return withCache(createArchetypeProvider({
|
|
615
|
+
archetype: entityDef.data.archetype,
|
|
616
|
+
archetypeKind: entityDef.data.archetypeKind,
|
|
617
|
+
projectTable: entityDef.data.table,
|
|
618
|
+
tenantId,
|
|
619
|
+
searchColumns: entityDef.data.searchColumns ?? entityDef.fields.filter((field) => field.searchable).map((field) => field.key)
|
|
620
|
+
}), cacheOptions);
|
|
621
|
+
}
|
|
622
|
+
return withCache(createSupabaseProvider(entityDef.data.table, {
|
|
623
|
+
schema: entityDef.data.schema,
|
|
624
|
+
tenantId: entityDef.data.tenantScoped === false ? void 0 : tenantId,
|
|
625
|
+
tenantIdColumn: entityDef.data.tenantIdColumn,
|
|
626
|
+
searchColumns: entityDef.data.searchColumns ?? entityDef.fields.filter((field) => field.searchable).map((field) => field.key),
|
|
627
|
+
selectColumns: entityDef.data.selectColumns,
|
|
628
|
+
columnMap: entityDef.data.columnMap,
|
|
629
|
+
filters: entityDef.data.filters,
|
|
630
|
+
defaults: entityDef.data.defaults
|
|
631
|
+
}), cacheOptions);
|
|
632
|
+
}
|
|
633
|
+
if (entityDef.data?.table) {
|
|
634
|
+
console.warn(
|
|
635
|
+
`[fayz/core] resolveDataProvider: falling back to mock for "${entityDef.name}" (table: ${entityDef.data.table}). Supabase client ${client ? "available" : "NOT initialized"}.`
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
return createMockProvider(entityDef, mockData);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export { clearGlobalCache, createArchetypeProvider, createCacheStore, createFayzApiProvider, createMockProvider, createSupabaseProvider, getActiveTenantId, getSupabaseClientOptional, globalCache, resolveDataProvider, setActiveTenantId, setGlobalSupabaseClient, stableKey, withCache };
|
|
642
|
+
//# sourceMappingURL=chunk-YSBXGHAM.js.map
|
|
643
|
+
//# sourceMappingURL=chunk-YSBXGHAM.js.map
|