@fayz-ai/core 0.9.0 → 0.11.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 (218) hide show
  1. package/README.md +2 -2
  2. package/dist/access/index.js.map +1 -1
  3. package/dist/chunk-BEGXYHS4.js +566 -0
  4. package/dist/chunk-BEGXYHS4.js.map +1 -0
  5. package/dist/chunk-ED6NBH6U.js +348 -0
  6. package/dist/chunk-ED6NBH6U.js.map +1 -0
  7. package/dist/chunk-EWKX4VUM.js +429 -0
  8. package/dist/chunk-EWKX4VUM.js.map +1 -0
  9. package/dist/chunk-LARZWDHA.js +319 -0
  10. package/dist/chunk-LARZWDHA.js.map +1 -0
  11. package/dist/chunk-OXQGRJZT.js +125 -0
  12. package/dist/chunk-OXQGRJZT.js.map +1 -0
  13. package/dist/chunk-RRYFXUQX.js +1247 -0
  14. package/dist/chunk-RRYFXUQX.js.map +1 -0
  15. package/dist/{chunk-5RJVUC6J.js → chunk-TDI6F6PT.js} +373 -25
  16. package/dist/chunk-TDI6F6PT.js.map +1 -0
  17. package/dist/{chunk-V2MSAUHO.js → chunk-UVKH6RDX.js} +211 -335
  18. package/dist/chunk-UVKH6RDX.js.map +1 -0
  19. package/dist/chunk-V53GR4UT.js +289 -0
  20. package/dist/chunk-V53GR4UT.js.map +1 -0
  21. package/dist/connector-runtime/index.d.ts +15 -0
  22. package/dist/connector-runtime/index.d.ts.map +1 -0
  23. package/dist/connector-runtime/index.js +3 -0
  24. package/dist/connector-runtime/index.js.map +1 -0
  25. package/dist/data/archetype.d.ts.map +1 -1
  26. package/dist/data/backend-env.d.ts +38 -0
  27. package/dist/data/backend-env.d.ts.map +1 -0
  28. package/dist/data/bulk.d.ts +24 -0
  29. package/dist/data/bulk.d.ts.map +1 -1
  30. package/dist/data/cached.d.ts.map +1 -1
  31. package/dist/data/custom-fields.d.ts +5 -0
  32. package/dist/data/custom-fields.d.ts.map +1 -0
  33. package/dist/data/index.d.ts +2 -1
  34. package/dist/data/index.d.ts.map +1 -1
  35. package/dist/data/index.js +3 -1
  36. package/dist/data/mock.d.ts.map +1 -1
  37. package/dist/data/platform-api.d.ts.map +1 -1
  38. package/dist/data/resolve.d.ts.map +1 -1
  39. package/dist/data/supabase.d.ts.map +1 -1
  40. package/dist/data/types.d.ts +8 -0
  41. package/dist/data/types.d.ts.map +1 -1
  42. package/dist/entity/events.d.ts +14 -0
  43. package/dist/entity/events.d.ts.map +1 -0
  44. package/dist/events/envelope.d.ts +31 -0
  45. package/dist/events/envelope.d.ts.map +1 -0
  46. package/dist/events/index.d.ts +4 -0
  47. package/dist/events/index.d.ts.map +1 -1
  48. package/dist/events/log.d.ts +44 -0
  49. package/dist/events/log.d.ts.map +1 -0
  50. package/dist/events/types.d.ts +40 -0
  51. package/dist/events/types.d.ts.map +1 -0
  52. package/dist/fields/store.d.ts +45 -0
  53. package/dist/fields/store.d.ts.map +1 -0
  54. package/dist/i18n/index.d.ts +10 -0
  55. package/dist/i18n/index.d.ts.map +1 -1
  56. package/dist/i18n/index.js +1 -1
  57. package/dist/i18n/shell-translations.en.d.ts.map +1 -1
  58. package/dist/i18n/shell-translations.pt-br.d.ts.map +1 -1
  59. package/dist/index.d.ts +45 -21
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +543 -56
  62. package/dist/index.js.map +1 -1
  63. package/dist/integrations/events.d.ts +7 -0
  64. package/dist/integrations/events.d.ts.map +1 -0
  65. package/dist/integrations/index.d.ts +533 -29
  66. package/dist/integrations/index.d.ts.map +1 -1
  67. package/dist/integrations/index.js +3 -1
  68. package/dist/integrations/permissions.d.ts +132 -0
  69. package/dist/integrations/permissions.d.ts.map +1 -0
  70. package/dist/integrations/request.d.ts +170 -0
  71. package/dist/integrations/request.d.ts.map +1 -0
  72. package/dist/integrations/runtime-token.d.ts +33 -0
  73. package/dist/integrations/runtime-token.d.ts.map +1 -0
  74. package/dist/integrations/runtime.d.ts +398 -0
  75. package/dist/integrations/runtime.d.ts.map +1 -0
  76. package/dist/integrations/settings.d.ts +102 -0
  77. package/dist/integrations/settings.d.ts.map +1 -0
  78. package/dist/integrations/store.d.ts +145 -0
  79. package/dist/integrations/store.d.ts.map +1 -0
  80. package/dist/{address/index.d.ts → lib/address.d.ts} +1 -1
  81. package/dist/{address/index.d.ts.map → lib/address.d.ts.map} +1 -1
  82. package/dist/{phone/index.d.ts → lib/phone.d.ts} +1 -1
  83. package/dist/lib/phone.d.ts.map +1 -0
  84. package/dist/{seo/index.d.ts → lib/seo.d.ts} +18 -1
  85. package/dist/lib/seo.d.ts.map +1 -0
  86. package/dist/{tenant/index.d.ts → lib/tenant.d.ts} +1 -1
  87. package/dist/lib/tenant.d.ts.map +1 -0
  88. package/dist/manifest/index.d.ts +1 -1
  89. package/dist/manifest/index.d.ts.map +1 -1
  90. package/dist/messaging/index.d.ts +3 -0
  91. package/dist/messaging/index.d.ts.map +1 -0
  92. package/dist/messaging/registry.d.ts +35 -0
  93. package/dist/messaging/registry.d.ts.map +1 -0
  94. package/dist/messaging/types.d.ts +67 -0
  95. package/dist/messaging/types.d.ts.map +1 -0
  96. package/dist/plugin/index.js +3 -1
  97. package/dist/plugin/runtime.d.ts +17 -0
  98. package/dist/plugin/runtime.d.ts.map +1 -1
  99. package/dist/registry/block-catalog.d.ts +78 -0
  100. package/dist/registry/block-catalog.d.ts.map +1 -0
  101. package/dist/registry/block-constraints.d.ts +64 -0
  102. package/dist/registry/block-constraints.d.ts.map +1 -0
  103. package/dist/registry/block-schema.d.ts +69 -0
  104. package/dist/registry/block-schema.d.ts.map +1 -0
  105. package/dist/registry/blocks.d.ts +84 -0
  106. package/dist/registry/blocks.d.ts.map +1 -0
  107. package/dist/registry/entities.d.ts +179 -0
  108. package/dist/registry/entities.d.ts.map +1 -0
  109. package/dist/registry/extension-points.d.ts +117 -0
  110. package/dist/registry/extension-points.d.ts.map +1 -0
  111. package/dist/registry/handles.d.ts +41 -0
  112. package/dist/registry/handles.d.ts.map +1 -0
  113. package/dist/registry/index.d.ts +41 -41
  114. package/dist/registry/index.d.ts.map +1 -1
  115. package/dist/registry/loaders.d.ts +55 -0
  116. package/dist/registry/loaders.d.ts.map +1 -0
  117. package/dist/registry/registry-class.d.ts +157 -0
  118. package/dist/registry/registry-class.d.ts.map +1 -0
  119. package/dist/registry/schema-check.d.ts +14 -0
  120. package/dist/registry/schema-check.d.ts.map +1 -0
  121. package/dist/router/index.d.ts +78 -1
  122. package/dist/router/index.d.ts.map +1 -1
  123. package/dist/router/index.js +1 -1
  124. package/dist/{shell-translations.pt-br-2GWRQRRP.js → shell-translations.pt-br-BM5ZFSKJ.js} +72 -3
  125. package/dist/shell-translations.pt-br-BM5ZFSKJ.js.map +1 -0
  126. package/dist/telemetry/errors.d.ts +87 -0
  127. package/dist/telemetry/errors.d.ts.map +1 -0
  128. package/dist/testing/index.js +4 -1
  129. package/dist/{testing/index.d.ts → testing.d.ts} +5 -3
  130. package/dist/testing.d.ts.map +1 -0
  131. package/dist/types/analytics.d.ts +213 -0
  132. package/dist/types/analytics.d.ts.map +1 -0
  133. package/dist/types/crud.d.ts +37 -3
  134. package/dist/types/crud.d.ts.map +1 -1
  135. package/dist/types/plugins.d.ts +138 -1
  136. package/dist/types/plugins.d.ts.map +1 -1
  137. package/package.json +9 -17
  138. package/dist/blocks/index.d.ts +0 -25
  139. package/dist/blocks/index.d.ts.map +0 -1
  140. package/dist/chunk-5RJVUC6J.js.map +0 -1
  141. package/dist/chunk-L4TLMYGC.js +0 -42
  142. package/dist/chunk-L4TLMYGC.js.map +0 -1
  143. package/dist/chunk-V2MSAUHO.js.map +0 -1
  144. package/dist/chunk-X5THHG6T.js +0 -77
  145. package/dist/chunk-X5THHG6T.js.map +0 -1
  146. package/dist/chunk-Y2C2FBQP.js +0 -35
  147. package/dist/chunk-Y2C2FBQP.js.map +0 -1
  148. package/dist/chunk-YH25Y4FW.js +0 -311
  149. package/dist/chunk-YH25Y4FW.js.map +0 -1
  150. package/dist/phone/index.d.ts.map +0 -1
  151. package/dist/seo/index.d.ts.map +0 -1
  152. package/dist/shell-translations.pt-br-2GWRQRRP.js.map +0 -1
  153. package/dist/tenant/index.d.ts.map +0 -1
  154. package/dist/testing/index.d.ts.map +0 -1
  155. package/src/access/denial.ts +0 -33
  156. package/src/access/engine.test.ts +0 -48
  157. package/src/access/engine.ts +0 -136
  158. package/src/access/index.ts +0 -14
  159. package/src/access/limits.ts +0 -18
  160. package/src/address/index.ts +0 -188
  161. package/src/app/render.tsx +0 -69
  162. package/src/blocks/index.tsx +0 -68
  163. package/src/data/archetype.ts +0 -255
  164. package/src/data/bulk.ts +0 -35
  165. package/src/data/cached.ts +0 -55
  166. package/src/data/count.test.ts +0 -152
  167. package/src/data/count.ts +0 -131
  168. package/src/data/filters.test.ts +0 -70
  169. package/src/data/filters.ts +0 -90
  170. package/src/data/index.ts +0 -11
  171. package/src/data/mock.ts +0 -90
  172. package/src/data/platform-api.ts +0 -209
  173. package/src/data/refresh.ts +0 -115
  174. package/src/data/resolve.ts +0 -126
  175. package/src/data/supabase.ts +0 -230
  176. package/src/data/types.ts +0 -50
  177. package/src/entity/index.ts +0 -2
  178. package/src/entity/registry.ts +0 -49
  179. package/src/events/index.tsx +0 -82
  180. package/src/i18n/index.ts +0 -476
  181. package/src/i18n/shell-translations.en.ts +0 -875
  182. package/src/i18n/shell-translations.pt-br.ts +0 -873
  183. package/src/index.ts +0 -217
  184. package/src/integrations/index.ts +0 -237
  185. package/src/lib/cache.ts +0 -118
  186. package/src/lib/csv.ts +0 -54
  187. package/src/lib/format.ts +0 -58
  188. package/src/manifest/app-manifest.schema.json +0 -676
  189. package/src/manifest/index.ts +0 -518
  190. package/src/manifest/serialize.ts +0 -52
  191. package/src/phone/index.ts +0 -85
  192. package/src/plugin/createSafeDataProvider.ts +0 -30
  193. package/src/plugin/index.ts +0 -1
  194. package/src/plugin/runtime.ts +0 -331
  195. package/src/registry/index.ts +0 -163
  196. package/src/router/history.ts +0 -86
  197. package/src/router/index.ts +0 -29
  198. package/src/runtime/index.ts +0 -15
  199. package/src/runtime/oauth.ts +0 -219
  200. package/src/search/engine.ts +0 -559
  201. package/src/search/index.ts +0 -25
  202. package/src/search/text.ts +0 -206
  203. package/src/search/types.ts +0 -75
  204. package/src/seo/index.ts +0 -128
  205. package/src/tenant/index.ts +0 -38
  206. package/src/testing/index.ts +0 -116
  207. package/src/types/auth.ts +0 -41
  208. package/src/types/billing.ts +0 -39
  209. package/src/types/crud.ts +0 -184
  210. package/src/types/entities.ts +0 -142
  211. package/src/types/entitlements.ts +0 -45
  212. package/src/types/i18n.ts +0 -7
  213. package/src/types/index.ts +0 -11
  214. package/src/types/org.ts +0 -137
  215. package/src/types/payment.ts +0 -44
  216. package/src/types/permissions.ts +0 -32
  217. package/src/types/plugins.ts +0 -480
  218. package/src/types/theme.ts +0 -8
@@ -1,126 +0,0 @@
1
- import type { EntityDef } from '../types/crud'
2
- import type { BackendRef } from '../manifest'
3
- import type { DataProvider } from './types'
4
- import { getSupabaseClientOptional } from './supabase'
5
- import { createSupabaseProvider } from './supabase'
6
- import { createArchetypeProvider } from './archetype'
7
- import { createMockProvider } from './mock'
8
- import { createFayzApiProvider, type FayzApiProviderConfig } from './platform-api'
9
- import { withCache } from './cached'
10
- import { getActiveTenantId } from '../tenant'
11
-
12
- /**
13
- * Resolve the best data provider for an EntityDef.
14
- * Supabase → Archetype → Mock (in order of preference).
15
- *
16
- * Reusable by createCrudPage, PluginRegistryManager, or any component
17
- * that needs a DataProvider from an EntityDef.
18
- */
19
- export interface ResolveDataProviderOptions {
20
- backend?: BackendRef
21
- fayzApi?: Omit<FayzApiProviderConfig, 'entityKey' | 'table'>
22
- /**
23
- * Read through the shared TTL cache (default true).
24
- *
25
- * Callers that must observe writes made by ANOTHER surface pass false: the
26
- * cache is only invalidated by writes that go through it, so a record created
27
- * on the agenda screen stays invisible to the assistant for the rest of the
28
- * TTL — which is how "o que tem na agenda hoje?" answered "nada" over a
29
- * booking made seconds earlier.
30
- */
31
- cache?: boolean
32
- customProviders?: Record<string, <T extends { id: string }>(entityDef: EntityDef<T>, mockData?: T[]) => DataProvider<T>>
33
- }
34
-
35
- export function resolveDataProvider<T extends { id: string }>(
36
- entityDef: EntityDef<T>,
37
- mockData?: T[],
38
- options: ResolveDataProviderOptions = {},
39
- ): DataProvider<T> {
40
- const backend = options.backend
41
-
42
- if (backend?.provider === 'mock') {
43
- return createMockProvider(entityDef, mockData)
44
- }
45
-
46
- if (backend?.provider === 'fayz-api') {
47
- const table = entityDef.data?.table ?? entityDef.name
48
- return createFayzApiProvider<T>(table, {
49
- ...options.fayzApi,
50
- baseUrl: options.fayzApi?.baseUrl ?? backend.url,
51
- projectId: options.fayzApi?.projectId ?? backend.projectRef,
52
- entityKey: entityDef.name ?? table,
53
- table,
54
- schema: options.fayzApi?.schema ?? entityDef.data?.schema,
55
- idColumn: options.fayzApi?.idColumn ?? entityDef.data?.columnMap?.id,
56
- searchColumns: options.fayzApi?.searchColumns ?? entityDef.data?.searchColumns ?? entityDef.fields
57
- .filter((field) => field.searchable)
58
- .map((field) => field.key),
59
- tenantIdColumn: entityDef.data?.tenantScoped === false
60
- ? false
61
- : options.fayzApi?.tenantIdColumn ?? entityDef.data?.tenantIdColumn,
62
- tenantId: options.fayzApi?.tenantId ?? (() => getActiveTenantId()),
63
- })
64
- }
65
-
66
- if (backend?.provider === 'custom') {
67
- const adapterId = backend.adapterId
68
- const factory = adapterId ? options.customProviders?.[adapterId] : undefined
69
- if (!adapterId || !factory) {
70
- throw new Error(`[@fayz-ai/core] Custom data provider "${adapterId ?? 'unknown'}" is not registered.`)
71
- }
72
- return factory<T>(entityDef, mockData)
73
- }
74
-
75
- const client = getSupabaseClientOptional()
76
-
77
- if (client && entityDef.data?.table) {
78
- const tenantId = () => getActiveTenantId()
79
- // Archetype entities share a physical table (e.g. people) across kinds
80
- // (student/teacher/staff…). The cache key must carry the kind or the first
81
- // kind's resultset is served to every sibling entity (teachers page
82
- // listing students).
83
- const cacheOptions = {
84
- table: entityDef.data.archetypeKind
85
- ? `${entityDef.data.table}#${entityDef.data.archetypeKind}`
86
- : entityDef.data.table,
87
- tenantId,
88
- ttl: entityDef.data.cacheTTL,
89
- }
90
- const cached = <P extends DataProvider<T>>(p: P): DataProvider<T> =>
91
- options.cache === false ? p : withCache(p, cacheOptions)
92
-
93
- if (entityDef.data.archetype && entityDef.data.archetypeKind && !entityDef.data.schema) {
94
- return cached(createArchetypeProvider<T>({
95
- archetype: entityDef.data.archetype,
96
- archetypeKind: entityDef.data.archetypeKind,
97
- projectTable: entityDef.data.table,
98
- tenantId,
99
- searchColumns: entityDef.data.searchColumns ?? entityDef.fields
100
- .filter((field) => field.searchable)
101
- .map((field) => field.key),
102
- }))
103
- }
104
-
105
- return cached(createSupabaseProvider<T>(entityDef.data.table, {
106
- schema: entityDef.data.schema,
107
- tenantId: entityDef.data.tenantScoped === false ? undefined : tenantId,
108
- tenantIdColumn: entityDef.data.tenantIdColumn,
109
- searchColumns: entityDef.data.searchColumns ?? entityDef.fields
110
- .filter((field) => field.searchable)
111
- .map((field) => field.key),
112
- selectColumns: entityDef.data.selectColumns,
113
- columnMap: entityDef.data.columnMap,
114
- filters: entityDef.data.filters,
115
- defaults: entityDef.data.defaults,
116
- }))
117
- }
118
-
119
- if (entityDef.data?.table) {
120
- console.warn(
121
- `[fayz/core] resolveDataProvider: falling back to mock for "${entityDef.name}" (table: ${entityDef.data.table}). ` +
122
- `Supabase client ${client ? 'available' : 'NOT initialized'}.`,
123
- )
124
- }
125
- return createMockProvider(entityDef, mockData)
126
- }
@@ -1,230 +0,0 @@
1
- import type { DataProvider, CrudQuery, CrudResult } from './types'
2
- import { applyRangeToQuery, isFilterObject, parseRangeFilter } from './filters'
3
-
4
- export interface SupabaseProviderConfig {
5
- schema?: string
6
- tenantId?: string | (() => string | undefined)
7
- tenantIdColumn?: string
8
- searchColumns?: string[]
9
- selectColumns?: string
10
- columnMap?: Record<string, string>
11
- filters?: Record<string, string>
12
- defaults?: Record<string, unknown>
13
- supabaseClient?: unknown
14
- }
15
-
16
- function camelToSnake(str: string): string {
17
- return str.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase())
18
- }
19
-
20
- function snakeToCamel(str: string): string {
21
- return str.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase())
22
- }
23
-
24
- function mapRow<T>(row: Record<string, unknown>, columnMap?: Record<string, string>): T {
25
- const result: Record<string, unknown> = {}
26
- for (const [key, value] of Object.entries(row)) {
27
- const camelKey = columnMap?.[key] ?? snakeToCamel(key)
28
- result[camelKey] = value
29
- }
30
- return result as T
31
- }
32
-
33
- function mapEntity(entity: Record<string, unknown>, columnMap?: Record<string, string>): Record<string, unknown> {
34
- const reverseMap: Record<string, string> = {}
35
- if (columnMap) {
36
- for (const [snake, camel] of Object.entries(columnMap)) {
37
- reverseMap[camel] = snake
38
- }
39
- }
40
- const result: Record<string, unknown> = {}
41
- for (const [key, value] of Object.entries(entity)) {
42
- if (key === 'id' || key === 'createdAt' || key === 'updatedAt' || key === 'tenantId') continue
43
- const snakeKey = reverseMap[key] ?? camelToSnake(key)
44
- result[snakeKey] = value
45
- }
46
- return result
47
- }
48
-
49
- // Global Supabase client registry — set once via createFayzApp
50
- let _globalSupabaseClient: unknown | null = null
51
-
52
- export function setGlobalSupabaseClient(client: unknown): void {
53
- _globalSupabaseClient = client
54
- }
55
-
56
- export function getSupabaseClientOptional(): unknown | null {
57
- return _globalSupabaseClient
58
- }
59
-
60
- export function createSupabaseProvider<T extends { id: string }>(
61
- table: string,
62
- config?: SupabaseProviderConfig,
63
- ): DataProvider<T> {
64
- const schema = config?.schema ?? 'public'
65
- const tenantIdCol = config?.tenantIdColumn ?? 'tenant_id'
66
- const searchCols = config?.searchColumns ?? []
67
- const selectCols = config?.selectColumns ?? '*'
68
- const columnMap = config?.columnMap
69
-
70
- function resolveTenantId(): string | undefined {
71
- if (!config?.tenantId) return undefined
72
- return typeof config.tenantId === 'function' ? config.tenantId() : config.tenantId
73
- }
74
-
75
- function getClient() {
76
- const supabase = (config?.supabaseClient ?? _globalSupabaseClient) as {
77
- schema: (s: string) => unknown
78
- from: (t: string) => unknown
79
- } | null
80
- if (!supabase) throw new Error(`[@fayz-ai/core] Supabase client not available. Call createFayzApp with a provider first.`)
81
- return schema === 'public' ? (supabase as { from: (t: string) => unknown }) : (supabase.schema(schema) as { from: (t: string) => unknown })
82
- }
83
-
84
- return {
85
- async list(query: CrudQuery): Promise<CrudResult<T>> {
86
- // 'none' skips PostgREST's count aggregate entirely — see CrudQuery.countMode.
87
- const wantCount = query.countMode !== 'none'
88
- let q = (getClient().from(table) as { select: (...args: unknown[]) => unknown })
89
- .select(selectCols, wantCount ? { count: 'exact' } : undefined) as Record<string, unknown>
90
-
91
- const tenantId = resolveTenantId()
92
- if (tenantId) q = (q as { eq: (col: string, val: string) => unknown }).eq(tenantIdCol, tenantId) as Record<string, unknown>
93
-
94
- if (config?.filters) {
95
- for (const [col, val] of Object.entries(config.filters)) {
96
- q = (q as { eq: (c: string, v: string) => unknown }).eq(col, val) as Record<string, unknown>
97
- }
98
- }
99
-
100
- // Faceted / dynamic filters from the query, mapped to columns. Equality
101
- // by default; a range object ({from,to} / {gte,lt} / {$gte,$lt}) becomes
102
- // real SQL bounds instead of an equality compare against "[object Object]".
103
- if (query.filters) {
104
- for (const [key, val] of Object.entries(query.filters)) {
105
- if (val == null || val === '') continue
106
- const col = columnMap?.[key] ?? camelToSnake(key)
107
- const range = parseRangeFilter(val)
108
- if (range) {
109
- q = applyRangeToQuery(q, col, range)
110
- continue
111
- }
112
- if (isFilterObject(val)) {
113
- throw new Error(
114
- `[@fayz-ai/core] Filter "${key}" is an object with no recognised bound. ` +
115
- 'Use {from,to} / {gte,lte,gt,lt} for ranges, or a plain value for equality.',
116
- )
117
- }
118
- q = (q as { eq: (c: string, v: unknown) => unknown }).eq(col, val) as Record<string, unknown>
119
- }
120
- }
121
-
122
- if (query.search && searchCols.length > 0) {
123
- const term = `%${query.search}%`
124
- const orClause = searchCols.map((col) => `${camelToSnake(col)}.ilike.${term}`).join(',')
125
- q = (q as { or: (c: string) => unknown }).or(orClause) as Record<string, unknown>
126
- }
127
-
128
- if (query.sortBy) {
129
- q = (q as { order: (col: string, opts: unknown) => unknown })
130
- .order(camelToSnake(query.sortBy), { ascending: query.sortDir !== 'desc' }) as Record<string, unknown>
131
- } else {
132
- q = (q as { order: (col: string, opts: unknown) => unknown })
133
- .order('created_at', { ascending: false }) as Record<string, unknown>
134
- }
135
-
136
- const page = query.page ?? 1
137
- const pageSize = query.pageSize ?? 50
138
- const from = (page - 1) * pageSize
139
- q = (q as { range: (f: number, t: number) => unknown }).range(from, from + pageSize - 1) as Record<string, unknown>
140
-
141
- const { data, error, count } = await q as { data: Record<string, unknown>[] | null; error: unknown; count: number | null }
142
- if (error) throw error
143
-
144
- return {
145
- data: (data ?? []).map((row) => mapRow<T>(row, columnMap)),
146
- total: count ?? 0,
147
- }
148
- },
149
-
150
- async create(data) {
151
- const row = mapEntity(data as Record<string, unknown>, columnMap)
152
- const tenantId = resolveTenantId()
153
- if (tenantId) row[tenantIdCol] = tenantId
154
- if (config?.defaults) {
155
- for (const [col, val] of Object.entries(config.defaults)) {
156
- if (row[col] === undefined) row[col] = val
157
- }
158
- }
159
-
160
- const { data: created, error } = (await (getClient().from(table) as {
161
- insert: (r: unknown) => { select: () => { single: () => Promise<{ data: unknown; error: unknown }> } }
162
- }).insert(row).select().single()) as { data: Record<string, unknown> | null; error: unknown }
163
-
164
- if (error) throw error
165
- return mapRow<T>(created!, columnMap)
166
- },
167
-
168
- async createMany(rows, options) {
169
- const tenantId = resolveTenantId()
170
- const size = Math.max(1, options?.chunkSize ?? 200)
171
- const prepared = rows.map((r) => {
172
- const row = mapEntity(r as Record<string, unknown>, columnMap)
173
- if (tenantId) row[tenantIdCol] = tenantId
174
- if (config?.defaults) {
175
- for (const [col, val] of Object.entries(config.defaults)) {
176
- if (row[col] === undefined) row[col] = val
177
- }
178
- }
179
- return row
180
- })
181
-
182
- let created = 0
183
- const failed: Array<{ index: number; message: string }> = []
184
- for (let start = 0; start < prepared.length; start += size) {
185
- const chunk = prepared.slice(start, start + size)
186
- const { error } = (await (getClient().from(table) as {
187
- insert: (r: unknown) => Promise<{ error: unknown }>
188
- }).insert(chunk)) as { error: unknown }
189
- if (!error) {
190
- created += chunk.length
191
- } else {
192
- // One bad row rejects its whole chunk. Retrying it row by row keeps
193
- // the other 199 — an import that loses 200 records over one malformed
194
- // phone number is worse than no import.
195
- const message = error instanceof Error ? error.message : String(error)
196
- for (let i = 0; i < chunk.length; i++) {
197
- const { error: rowError } = (await (getClient().from(table) as {
198
- insert: (r: unknown) => Promise<{ error: unknown }>
199
- }).insert(chunk[i])) as { error: unknown }
200
- if (rowError) {
201
- failed.push({
202
- index: start + i,
203
- message: rowError instanceof Error ? rowError.message : String(rowError ?? message),
204
- })
205
- } else created += 1
206
- }
207
- }
208
- options?.onProgress?.(Math.min(start + chunk.length, prepared.length), prepared.length)
209
- }
210
- return { created, failed }
211
- },
212
-
213
- async update(id, data) {
214
- const row = mapEntity(data as Record<string, unknown>, columnMap)
215
- const { data: updated, error } = (await (getClient().from(table) as {
216
- update: (r: unknown) => { eq: (c: string, v: string) => { select: () => { single: () => Promise<{ data: unknown; error: unknown }> } } }
217
- }).update(row).eq('id', id).select().single()) as { data: Record<string, unknown> | null; error: unknown }
218
-
219
- if (error) throw error
220
- return mapRow<T>(updated!, columnMap)
221
- },
222
-
223
- async remove(id) {
224
- const { error } = (await (getClient().from(table) as {
225
- delete: () => { eq: (c: string, v: string) => Promise<{ error: unknown }> }
226
- }).delete().eq('id', id)) as { error: unknown }
227
- if (error) throw error
228
- },
229
- }
230
- }
package/src/data/types.ts DELETED
@@ -1,50 +0,0 @@
1
- export interface CrudQuery {
2
- search?: string
3
- sortBy?: string
4
- sortDir?: 'asc' | 'desc'
5
- page?: number
6
- pageSize?: number
7
- filters?: Record<string, unknown>
8
- /**
9
- * Default 'exact' — a CRUD pager needs the total. It is a second aggregate
10
- * over the filtered set, and on the live pool cost ~1070ms against ~270ms for
11
- * the rows themselves. Callers that only want rows pass 'none' (total: 0).
12
- */
13
- countMode?: 'exact' | 'none'
14
- }
15
-
16
- export interface CrudResult<T> {
17
- data: T[]
18
- total: number
19
- }
20
-
21
- /** Outcome of a bulk write: what landed, and what did not and why. */
22
- export interface BulkWriteResult {
23
- created: number
24
- failed: Array<{ index: number; message: string }>
25
- }
26
-
27
- export interface BulkWriteOptions {
28
- /** Rows per round-trip. Larger is fewer trips; too large trips statement timeouts. */
29
- chunkSize?: number
30
- onProgress?: (done: number, total: number) => void
31
- }
32
-
33
- export interface DataProvider<T extends { id: string } = { id: string }> {
34
- list(query: CrudQuery): Promise<CrudResult<T>>
35
- create(data: Omit<T, 'id' | 'createdAt' | 'updatedAt' | 'tenantId'>): Promise<T>
36
- /**
37
- * Write many rows in as few round-trips as the backend allows.
38
- *
39
- * Optional: `bulkCreate` falls back to looping `create` for providers that do
40
- * not implement it, so a caller never has to branch. Importing a 900-row
41
- * client list one INSERT at a time is 900 round-trips — the difference
42
- * between a progress bar and a hung tab.
43
- */
44
- createMany?(
45
- rows: Array<Omit<T, 'id' | 'createdAt' | 'updatedAt' | 'tenantId'>>,
46
- options?: BulkWriteOptions,
47
- ): Promise<BulkWriteResult>
48
- update(id: string, data: Partial<T>): Promise<T>
49
- remove(id: string): Promise<void>
50
- }
@@ -1,2 +0,0 @@
1
- export { registerEntity, getEntityByKey, getAllEntities, clearEntityRegistry, deriveEntityKey } from './registry'
2
- export type { RegisteredEntity } from './registry'
@@ -1,49 +0,0 @@
1
- import type { EntityDef } from '../types/crud'
2
- import type { EntityArchetype } from '../types/entities'
3
-
4
- export interface RegisteredEntity {
5
- entityKey: string
6
- label: string
7
- labelPlural: string
8
- icon?: string
9
- fields: EntityDef['fields']
10
- source: 'app' | 'plugin'
11
- /**
12
- * The definition itself, so consumers that need to *read* the entity — the AI
13
- * tool executor resolving a data provider — are not limited to the display
14
- * metadata. Optional: registrations made before this existed omit it.
15
- */
16
- entityDef?: EntityDef
17
- mockData?: Array<{ id: string }>
18
- archetype?: EntityArchetype
19
- pluginId?: string
20
- pluginName?: string
21
- pluginIcon?: string
22
- }
23
-
24
- const entityRegistry = new Map<string, RegisteredEntity>()
25
-
26
- export function registerEntity(entry: RegisteredEntity): void {
27
- entityRegistry.set(entry.entityKey, entry)
28
- }
29
-
30
- export function getEntityByKey(key: string): RegisteredEntity | undefined {
31
- return entityRegistry.get(key)
32
- }
33
-
34
- export function getAllEntities(): RegisteredEntity[] {
35
- return Array.from(entityRegistry.values())
36
- }
37
-
38
- export function clearEntityRegistry(): void {
39
- entityRegistry.clear()
40
- }
41
-
42
- export function deriveEntityKey(entityDef: EntityDef): string {
43
- const name = entityDef.name.toLowerCase().replace(/\s+/g, '-')
44
- if (entityDef.data?.archetype && entityDef.data?.archetypeKind) {
45
- return `${entityDef.data.archetype}:${entityDef.data.archetypeKind}`
46
- }
47
- if (entityDef.data?.archetype) return entityDef.data.archetype
48
- return name
49
- }
@@ -1,82 +0,0 @@
1
- import * as React from 'react'
2
-
3
- // ---------------------------------------------------------------------------
4
- // Event bus — the substrate for cross-plugin behavior. Replaces the bespoke
5
- // `window.dispatchEvent('agenda:open-booking')` hack and the imperative
6
- // financial bridge: plugins emit named events, others subscribe, and the
7
- // AppManifest can declare event→action bindings as data. Plugins describe the
8
- // events they emit via PluginManifest.events (see types/plugins) so the
9
- // platform can introspect and wire automations.
10
- // ---------------------------------------------------------------------------
11
-
12
- export type EventHandler<T = unknown> = (payload: T) => void
13
-
14
- export interface EventBus {
15
- emit<T = unknown>(event: string, payload?: T): void
16
- on<T = unknown>(event: string, handler: EventHandler<T>): () => void
17
- once<T = unknown>(event: string, handler: EventHandler<T>): () => void
18
- off<T = unknown>(event: string, handler: EventHandler<T>): void
19
- clear(event?: string): void
20
- }
21
-
22
- export function createEventBus(): EventBus {
23
- const handlers = new Map<string, Set<EventHandler>>()
24
- const bus: EventBus = {
25
- emit(event, payload) {
26
- const set = handlers.get(event)
27
- if (!set) return
28
- // Snapshot so handlers can subscribe/unsubscribe during dispatch.
29
- for (const handler of Array.from(set)) {
30
- try {
31
- handler(payload)
32
- } catch (err) {
33
- // A misbehaving subscriber must not break the emitter or its peers.
34
- console.error(`[fayz] event handler for "${event}" threw:`, err)
35
- }
36
- }
37
- },
38
- on(event, handler) {
39
- let set = handlers.get(event)
40
- if (!set) {
41
- set = new Set()
42
- handlers.set(event, set)
43
- }
44
- set.add(handler as EventHandler)
45
- return () => bus.off(event, handler)
46
- },
47
- once(event, handler) {
48
- const wrapped: EventHandler = (payload) => {
49
- bus.off(event, wrapped)
50
- ;(handler as EventHandler)(payload)
51
- }
52
- return bus.on(event, wrapped)
53
- },
54
- off(event, handler) {
55
- handlers.get(event)?.delete(handler as EventHandler)
56
- },
57
- clear(event) {
58
- if (event) handlers.delete(event)
59
- else handlers.clear()
60
- },
61
- }
62
- return bus
63
- }
64
-
65
- /** The app-wide default bus. One per app runtime (repo-per-app). */
66
- export const eventBus: EventBus = createEventBus()
67
-
68
- /** Subscribe to an event for the lifetime of the component. The latest handler
69
- * is always invoked, so closures over fresh props/state work without resubscribing. */
70
- export function useOnEvent<T = unknown>(
71
- event: string,
72
- handler: EventHandler<T>,
73
- deps: React.DependencyList = [],
74
- ): void {
75
- const ref = React.useRef(handler)
76
- ref.current = handler
77
- React.useEffect(
78
- () => eventBus.on<T>(event, (payload) => ref.current(payload)),
79
- // eslint-disable-next-line react-hooks/exhaustive-deps
80
- [event, ...deps],
81
- )
82
- }