@fayz-ai/core 0.1.6 → 0.1.7
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/README.md +44 -0
- package/package.json +1 -1
- package/src/data/archetype.ts +34 -17
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @fayz-ai/core
|
|
2
|
+
|
|
3
|
+
> The headless kernel that turns plugin manifests into a running app.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@fayz-ai/core)
|
|
6
|
+
[](https://github.com/FayaLabs/fayz-sdk/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
A salon, a restaurant, and a clinic are the same engine with different plugins enabled. `@fayz-ai/core` is that engine — headless and UI-free. It defines the plugin contract, the entity/CRUD model, the data-provider abstraction, and the manifest format that describes an entire app as data. Plugins declare what they are; core resolves them into navigation, routes, widgets, and tools at runtime.
|
|
9
|
+
|
|
10
|
+
The bet: a SaaS app should be composable and portable, not hand-wired. Swap Supabase for the Fayz API or a mock with one provider call. Describe an app as a manifest and render it. Register an entity once and get list/form/detail behavior everywhere. Core is the substrate that makes plugins "snap in."
|
|
11
|
+
|
|
12
|
+
## What's inside
|
|
13
|
+
- **Plugin runtime** — `definePlugin`, `resolvePluginRuntime`, `getWidgetsForZone`, `getDashboardWidgets`, `PluginRuntimeProvider`, `PLUGIN_API_VERSION`
|
|
14
|
+
- **Data providers** — `createSupabaseProvider`, `createFayzApiProvider`, `createMockProvider`, `createArchetypeProvider`, `withCache`, `resolveDataProvider` over one `DataProvider` interface
|
|
15
|
+
- **Entity + registry** — `registerEntity`, `getEntityByKey`, and the uniform `Registry` for components, blocks, pages, metrics, scaffolds, and plugin factories
|
|
16
|
+
- **App manifest** — `defineApp`, `renderApp`, `migrateManifest`, `validateManifest`, JSON schema, and a versioned migration runner
|
|
17
|
+
- **Blocks** — `BlockRenderer` / `renderBlocks`, the universal page primitive
|
|
18
|
+
- **i18n + router** — `I18nProvider`, `useTranslation`, `hashRouterAdapter`, `windowRouterAdapter`
|
|
19
|
+
- **Event bus + utils** — `eventBus`, `useOnEvent`, `formatCurrency`, `exportCSV`, tenant context (`setActiveTenantId`)
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
```bash
|
|
23
|
+
npm install @fayz-ai/core
|
|
24
|
+
```
|
|
25
|
+
Peer deps: `react`, `react-dom` (^18 or ^19).
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
```ts
|
|
29
|
+
import { definePlugin, createSupabaseProvider } from '@fayz-ai/core'
|
|
30
|
+
|
|
31
|
+
const provider = createSupabaseProvider({ url, anonKey })
|
|
32
|
+
|
|
33
|
+
export const crmPlugin = definePlugin({
|
|
34
|
+
id: 'crm',
|
|
35
|
+
navigation: [{ label: 'Clients', path: '/clients' }],
|
|
36
|
+
entities: [/* declarative data models */],
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Part of the Fayz SDK
|
|
41
|
+
The headless core. `@fayz-ai/auth`, `@fayz-ai/ui`, and `@fayz-ai/saas` build the running app on top of it.
|
|
42
|
+
|
|
43
|
+
## Roadmap & contributing
|
|
44
|
+
Built and evolving in the open. See the [Fayz SDK roadmap](../../docs/ROADMAP.md#core) for current gaps, missing features, and good first issues.
|
package/package.json
CHANGED
package/src/data/archetype.ts
CHANGED
|
@@ -92,16 +92,27 @@ export function createArchetypeProvider<T extends { id: string }>(
|
|
|
92
92
|
}
|
|
93
93
|
const viewName = VIEW_MAP[config.projectTable] ?? `v_${config.projectTable}`
|
|
94
94
|
|
|
95
|
+
// Pure-archetype entity: the project table IS the archetype base (e.g. a
|
|
96
|
+
// customers list configured with `table: 'persons'`, no separate public
|
|
97
|
+
// extension table). Read/write saas_core.<base> directly — there is no public
|
|
98
|
+
// extension row and no v_ view to join. Extension-table entities (clients,
|
|
99
|
+
// staff_members, …) are unaffected.
|
|
100
|
+
const isPure = config.projectTable === ac.table
|
|
101
|
+
|
|
95
102
|
return {
|
|
96
103
|
async list(query: CrudQuery): Promise<CrudResult<T>> {
|
|
97
104
|
const tenantId = config.tenantId()
|
|
98
105
|
if (!tenantId) return { data: [], total: 0 }
|
|
99
106
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
.select('*', { count: 'exact' })
|
|
104
|
-
.eq('tenant_id', tenantId)
|
|
107
|
+
// Pure archetype → read saas_core.<base> directly (filtered by kind);
|
|
108
|
+
// otherwise the v_ view (JOINs public extension with saas_core archetype).
|
|
109
|
+
let q = isPure
|
|
110
|
+
? coreClient().from(ac.table).select('*', { count: 'exact' }).eq('tenant_id', tenantId)
|
|
111
|
+
: getClient().from(viewName).select('*', { count: 'exact' }).eq('tenant_id', tenantId)
|
|
112
|
+
|
|
113
|
+
if (isPure && HAS_KIND.has(config.archetype)) {
|
|
114
|
+
q = q.eq('kind', config.archetypeKind)
|
|
115
|
+
}
|
|
105
116
|
|
|
106
117
|
// Search
|
|
107
118
|
if (query.search && config.searchColumns && config.searchColumns.length > 0) {
|
|
@@ -155,20 +166,23 @@ export function createArchetypeProvider<T extends { id: string }>(
|
|
|
155
166
|
if (archetypeError) throw archetypeError
|
|
156
167
|
const archetypeId = (archetypeRow as any).id
|
|
157
168
|
|
|
158
|
-
// 2. Insert into project table with archetype FK as PK
|
|
159
|
-
|
|
160
|
-
|
|
169
|
+
// 2. Insert into project table with archetype FK as PK.
|
|
170
|
+
// Pure-archetype entities have no extension table — skip this step.
|
|
171
|
+
if (!isPure) {
|
|
172
|
+
projectData[fkColumn] = archetypeId
|
|
173
|
+
projectData.tenant_id = tenantId
|
|
161
174
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
175
|
+
const { error: projectError } = await getClient()
|
|
176
|
+
.from(config.projectTable)
|
|
177
|
+
.insert(projectData)
|
|
165
178
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
179
|
+
if (projectError) {
|
|
180
|
+
await coreClient().from(ac.table).delete().eq('id', archetypeId)
|
|
181
|
+
throw projectError
|
|
182
|
+
}
|
|
169
183
|
}
|
|
170
184
|
|
|
171
|
-
return { id: archetypeId, ...archetypeRow, ...projectData } as unknown as T
|
|
185
|
+
return { id: archetypeId, ...archetypeRow, ...(isPure ? {} : projectData) } as unknown as T
|
|
172
186
|
},
|
|
173
187
|
|
|
174
188
|
async update(id, data) {
|
|
@@ -182,7 +196,7 @@ export function createArchetypeProvider<T extends { id: string }>(
|
|
|
182
196
|
if (error) throw error
|
|
183
197
|
}
|
|
184
198
|
|
|
185
|
-
if (Object.keys(projectData).length > 0) {
|
|
199
|
+
if (!isPure && Object.keys(projectData).length > 0) {
|
|
186
200
|
const { error } = await getClient()
|
|
187
201
|
.from(config.projectTable)
|
|
188
202
|
.update(projectData)
|
|
@@ -190,8 +204,11 @@ export function createArchetypeProvider<T extends { id: string }>(
|
|
|
190
204
|
if (error) throw error
|
|
191
205
|
}
|
|
192
206
|
|
|
193
|
-
// Re-fetch
|
|
207
|
+
// Re-fetch (archetype only for pure entities; otherwise join the extension)
|
|
194
208
|
const { data: archetypeRow } = await coreClient().from(ac.table).select('*').eq('id', id).single()
|
|
209
|
+
if (isPure) {
|
|
210
|
+
return mapRow<T>({ ...archetypeRow, id } as Record<string, unknown>)
|
|
211
|
+
}
|
|
195
212
|
const { data: projectRow } = await getClient().from(config.projectTable).select('*').eq(fkColumn, id).single()
|
|
196
213
|
|
|
197
214
|
const flat = { ...archetypeRow, ...projectRow, id }
|