@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 ADDED
@@ -0,0 +1,44 @@
1
+ # @fayz-ai/core
2
+
3
+ > The headless kernel that turns plugin manifests into a running app.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@fayz-ai/core.svg)](https://www.npmjs.com/package/@fayz-ai/core)
6
+ [![license](https://img.shields.io/npm/l/@fayz-ai/core.svg)](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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fayz-ai/core",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Fayz SDK core — data providers, entity system, plugin runtime, i18n, routing",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -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
- // Single query via v_ view (JOINs public table with saas_core archetype)
101
- let q = getClient()
102
- .from(viewName)
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
- projectData[fkColumn] = archetypeId
160
- projectData.tenant_id = tenantId
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
- const { error: projectError } = await getClient()
163
- .from(config.projectTable)
164
- .insert(projectData)
175
+ const { error: projectError } = await getClient()
176
+ .from(config.projectTable)
177
+ .insert(projectData)
165
178
 
166
- if (projectError) {
167
- await coreClient().from(ac.table).delete().eq('id', archetypeId)
168
- throw projectError
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 }