@fayz-ai/core 0.9.0 → 0.10.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 (170) hide show
  1. package/README.md +2 -2
  2. package/dist/access/index.js.map +1 -1
  3. package/dist/{chunk-L4TLMYGC.js → chunk-6RBW2Y5K.js} +3 -3
  4. package/dist/chunk-6RBW2Y5K.js.map +1 -0
  5. package/dist/chunk-732GZEUI.js +1592 -0
  6. package/dist/chunk-732GZEUI.js.map +1 -0
  7. package/dist/{chunk-X5THHG6T.js → chunk-CPMHCTX3.js} +3 -3
  8. package/dist/chunk-CPMHCTX3.js.map +1 -0
  9. package/dist/{chunk-V2MSAUHO.js → chunk-JO7Z2AOE.js} +2 -2
  10. package/dist/chunk-JO7Z2AOE.js.map +1 -0
  11. package/dist/{chunk-5RJVUC6J.js → chunk-M27JPX3K.js} +12 -3
  12. package/dist/chunk-M27JPX3K.js.map +1 -0
  13. package/dist/chunk-OXQGRJZT.js +125 -0
  14. package/dist/chunk-OXQGRJZT.js.map +1 -0
  15. package/dist/data/backend-env.d.ts +38 -0
  16. package/dist/data/backend-env.d.ts.map +1 -0
  17. package/dist/data/index.js +1 -1
  18. package/dist/entity/events.d.ts +14 -0
  19. package/dist/entity/events.d.ts.map +1 -0
  20. package/dist/events/envelope.d.ts +31 -0
  21. package/dist/events/envelope.d.ts.map +1 -0
  22. package/dist/events/index.d.ts +4 -0
  23. package/dist/events/index.d.ts.map +1 -1
  24. package/dist/events/log.d.ts +44 -0
  25. package/dist/events/log.d.ts.map +1 -0
  26. package/dist/events/types.d.ts +40 -0
  27. package/dist/events/types.d.ts.map +1 -0
  28. package/dist/i18n/index.d.ts +10 -0
  29. package/dist/i18n/index.d.ts.map +1 -1
  30. package/dist/i18n/index.js +1 -1
  31. package/dist/index.d.ts +27 -17
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +388 -52
  34. package/dist/index.js.map +1 -1
  35. package/dist/integrations/index.js +1 -1
  36. package/dist/{integrations/index.d.ts → integrations.d.ts} +2 -2
  37. package/dist/integrations.d.ts.map +1 -0
  38. package/dist/{address/index.d.ts → lib/address.d.ts} +1 -1
  39. package/dist/{address/index.d.ts.map → lib/address.d.ts.map} +1 -1
  40. package/dist/{phone/index.d.ts → lib/phone.d.ts} +1 -1
  41. package/dist/lib/phone.d.ts.map +1 -0
  42. package/dist/{seo/index.d.ts → lib/seo.d.ts} +18 -1
  43. package/dist/lib/seo.d.ts.map +1 -0
  44. package/dist/{tenant/index.d.ts → lib/tenant.d.ts} +1 -1
  45. package/dist/lib/tenant.d.ts.map +1 -0
  46. package/dist/manifest/index.d.ts +1 -1
  47. package/dist/manifest/index.d.ts.map +1 -1
  48. package/dist/messaging/index.d.ts +3 -0
  49. package/dist/messaging/index.d.ts.map +1 -0
  50. package/dist/messaging/registry.d.ts +35 -0
  51. package/dist/messaging/registry.d.ts.map +1 -0
  52. package/dist/messaging/types.d.ts +67 -0
  53. package/dist/messaging/types.d.ts.map +1 -0
  54. package/dist/plugin/index.js +2 -1
  55. package/dist/plugin/runtime.d.ts +17 -0
  56. package/dist/plugin/runtime.d.ts.map +1 -1
  57. package/dist/registry/block-catalog.d.ts +78 -0
  58. package/dist/registry/block-catalog.d.ts.map +1 -0
  59. package/dist/registry/block-constraints.d.ts +64 -0
  60. package/dist/registry/block-constraints.d.ts.map +1 -0
  61. package/dist/registry/block-schema.d.ts +69 -0
  62. package/dist/registry/block-schema.d.ts.map +1 -0
  63. package/dist/registry/blocks.d.ts +84 -0
  64. package/dist/registry/blocks.d.ts.map +1 -0
  65. package/dist/registry/entities.d.ts +153 -0
  66. package/dist/registry/entities.d.ts.map +1 -0
  67. package/dist/registry/extension-points.d.ts +117 -0
  68. package/dist/registry/extension-points.d.ts.map +1 -0
  69. package/dist/registry/handles.d.ts +41 -0
  70. package/dist/registry/handles.d.ts.map +1 -0
  71. package/dist/registry/index.d.ts +41 -41
  72. package/dist/registry/index.d.ts.map +1 -1
  73. package/dist/registry/loaders.d.ts +55 -0
  74. package/dist/registry/loaders.d.ts.map +1 -0
  75. package/dist/registry/registry-class.d.ts +157 -0
  76. package/dist/registry/registry-class.d.ts.map +1 -0
  77. package/dist/registry/schema-check.d.ts +14 -0
  78. package/dist/registry/schema-check.d.ts.map +1 -0
  79. package/dist/router/index.d.ts +78 -1
  80. package/dist/router/index.d.ts.map +1 -1
  81. package/dist/router/index.js +1 -1
  82. package/dist/testing/index.js +1 -1
  83. package/dist/{testing/index.d.ts → testing.d.ts} +3 -3
  84. package/dist/testing.d.ts.map +1 -0
  85. package/dist/types/analytics.d.ts +196 -0
  86. package/dist/types/analytics.d.ts.map +1 -0
  87. package/dist/types/crud.d.ts +22 -2
  88. package/dist/types/crud.d.ts.map +1 -1
  89. package/dist/types/plugins.d.ts +55 -1
  90. package/dist/types/plugins.d.ts.map +1 -1
  91. package/package.json +6 -18
  92. package/dist/blocks/index.d.ts +0 -25
  93. package/dist/blocks/index.d.ts.map +0 -1
  94. package/dist/chunk-5RJVUC6J.js.map +0 -1
  95. package/dist/chunk-L4TLMYGC.js.map +0 -1
  96. package/dist/chunk-V2MSAUHO.js.map +0 -1
  97. package/dist/chunk-X5THHG6T.js.map +0 -1
  98. package/dist/chunk-Y2C2FBQP.js +0 -35
  99. package/dist/chunk-Y2C2FBQP.js.map +0 -1
  100. package/dist/chunk-YH25Y4FW.js +0 -311
  101. package/dist/chunk-YH25Y4FW.js.map +0 -1
  102. package/dist/integrations/index.d.ts.map +0 -1
  103. package/dist/phone/index.d.ts.map +0 -1
  104. package/dist/seo/index.d.ts.map +0 -1
  105. package/dist/tenant/index.d.ts.map +0 -1
  106. package/dist/testing/index.d.ts.map +0 -1
  107. package/src/access/denial.ts +0 -33
  108. package/src/access/engine.test.ts +0 -48
  109. package/src/access/engine.ts +0 -136
  110. package/src/access/index.ts +0 -14
  111. package/src/access/limits.ts +0 -18
  112. package/src/address/index.ts +0 -188
  113. package/src/app/render.tsx +0 -69
  114. package/src/blocks/index.tsx +0 -68
  115. package/src/data/archetype.ts +0 -255
  116. package/src/data/bulk.ts +0 -35
  117. package/src/data/cached.ts +0 -55
  118. package/src/data/count.test.ts +0 -152
  119. package/src/data/count.ts +0 -131
  120. package/src/data/filters.test.ts +0 -70
  121. package/src/data/filters.ts +0 -90
  122. package/src/data/index.ts +0 -11
  123. package/src/data/mock.ts +0 -90
  124. package/src/data/platform-api.ts +0 -209
  125. package/src/data/refresh.ts +0 -115
  126. package/src/data/resolve.ts +0 -126
  127. package/src/data/supabase.ts +0 -230
  128. package/src/data/types.ts +0 -50
  129. package/src/entity/index.ts +0 -2
  130. package/src/entity/registry.ts +0 -49
  131. package/src/events/index.tsx +0 -82
  132. package/src/i18n/index.ts +0 -476
  133. package/src/i18n/shell-translations.en.ts +0 -875
  134. package/src/i18n/shell-translations.pt-br.ts +0 -873
  135. package/src/index.ts +0 -217
  136. package/src/integrations/index.ts +0 -237
  137. package/src/lib/cache.ts +0 -118
  138. package/src/lib/csv.ts +0 -54
  139. package/src/lib/format.ts +0 -58
  140. package/src/manifest/app-manifest.schema.json +0 -676
  141. package/src/manifest/index.ts +0 -518
  142. package/src/manifest/serialize.ts +0 -52
  143. package/src/phone/index.ts +0 -85
  144. package/src/plugin/createSafeDataProvider.ts +0 -30
  145. package/src/plugin/index.ts +0 -1
  146. package/src/plugin/runtime.ts +0 -331
  147. package/src/registry/index.ts +0 -163
  148. package/src/router/history.ts +0 -86
  149. package/src/router/index.ts +0 -29
  150. package/src/runtime/index.ts +0 -15
  151. package/src/runtime/oauth.ts +0 -219
  152. package/src/search/engine.ts +0 -559
  153. package/src/search/index.ts +0 -25
  154. package/src/search/text.ts +0 -206
  155. package/src/search/types.ts +0 -75
  156. package/src/seo/index.ts +0 -128
  157. package/src/tenant/index.ts +0 -38
  158. package/src/testing/index.ts +0 -116
  159. package/src/types/auth.ts +0 -41
  160. package/src/types/billing.ts +0 -39
  161. package/src/types/crud.ts +0 -184
  162. package/src/types/entities.ts +0 -142
  163. package/src/types/entitlements.ts +0 -45
  164. package/src/types/i18n.ts +0 -7
  165. package/src/types/index.ts +0 -11
  166. package/src/types/org.ts +0 -137
  167. package/src/types/payment.ts +0 -44
  168. package/src/types/permissions.ts +0 -32
  169. package/src/types/plugins.ts +0 -480
  170. package/src/types/theme.ts +0 -8
@@ -1,518 +0,0 @@
1
- import type { EntityDef } from '../types/crud'
2
- import type { LocaleConfig } from '../types/i18n'
3
- import type { PermissionsConfig, FeatureDeclaration } from '../types/permissions'
4
- import type { BillingConfig } from '../types/billing'
5
- import type { LimitDeclaration } from '../types/entitlements'
6
- import type { AIToolParameters, AIToolExecution, AgentRpcDeclaration } from '../types/plugins'
7
- import type { BlockNode } from '../blocks'
8
-
9
- // ---------------------------------------------------------------------------
10
- // AppManifest — THE app, as data.
11
- //
12
- // This is the canonical artifact every generated repo commits as
13
- // `app.manifest.json` and that `fayz upgrade` migrates across the fleet. It is
14
- // strictly JSON-serializable: no functions, no components — only ids resolved
15
- // against the registries (see ../registry). `createSaasApp`/`createStorefrontApp`
16
- // become thin sugar that builds a manifest of this shape.
17
- //
18
- // Versioning: bump CURRENT_MANIFEST_VERSION and register a migration whenever
19
- // the shape changes. The single biggest fleet-maintenance lever — getting it
20
- // right is why W1 is the "point of no return".
21
- // ---------------------------------------------------------------------------
22
-
23
- export const CURRENT_MANIFEST_VERSION = 3
24
-
25
- export type BackendProvider = 'supabase' | 'fayz-api' | 'fayz-shop' | 'mock' | 'custom'
26
-
27
- export interface BackendRef {
28
- provider: BackendProvider
29
- /** Provider project ref / URL. Secrets stay in env/platform storage. */
30
- projectRef?: string
31
- url?: string
32
- /** Registered adapter id when provider is 'custom'. */
33
- adapterId?: string
34
- /** Public, JSON-safe provider options. Never store secrets here. */
35
- options?: Record<string, unknown>
36
- }
37
-
38
- /** How a manifest page produces its body — exactly one of blocks/entity/component. */
39
- export interface PageManifest {
40
- path: string
41
- label?: string
42
- icon?: string
43
- section?: 'main' | 'secondary' | 'settings'
44
- /** A block tree (pure data). */
45
- blocks?: BlockNode[]
46
- /** An entity key → rendered as a CRUD page by the scaffold. */
47
- entity?: string
48
- /** A registered page-component id, e.g. 'custom:HarvestBoard'. */
49
- component?: string
50
- /** Optional permission gate: feature + action. */
51
- permission?: { feature: string; action: string }
52
- }
53
-
54
- export interface PluginRef {
55
- /** Plugin id, resolved to an installed @fayz-ai/plugin-* at build time. */
56
- id: string
57
- /** Pure-data config passed to the plugin (labels, kinds, module flags…). */
58
- config?: Record<string, unknown>
59
- enabled?: boolean
60
- }
61
-
62
- export interface SurfaceManifest {
63
- /** Registered scaffold id: 'admin' | 'storefront' | 'portal' | custom. */
64
- scaffold: string
65
- plugins?: PluginRef[]
66
- pages?: PageManifest[]
67
- /** Surface-specific declarative options (announcement, footer, nav, shipping,
68
- * catalog seed, onboarding, …) — kept open so scaffolds evolve without a
69
- * manifest-version bump for additive fields. */
70
- options?: Record<string, unknown>
71
- }
72
-
73
- // ---------------------------------------------------------------------------
74
- // Agent Contract (manifest v3) — everything a SERVER-SIDE agent runtime needs
75
- // to operate the app without a browser: the full entity surface, the tool
76
- // catalog with its authorization requirements, the pool RPCs writes go
77
- // through, and the structured domain knowledge the app wants in the prompt.
78
- // Derived from app code at publish (never hand-written) and synced to the
79
- // Fayz platform, where it becomes the versioned AgentContract the broker
80
- // validates every turn against.
81
- // ---------------------------------------------------------------------------
82
-
83
- /** JSON-safe projection of an EntityDef — schema only, no components/closures.
84
- * `data` mirrors EntityDef.data: everything the server-plane read executor
85
- * needs (tenant scoping, search/select column allowlists, fixed filters). */
86
- export interface EntityContract {
87
- key: string
88
- label: string
89
- labelPlural?: string
90
- /** Plan-limit key rows of this entity consume (EntityDef.limitKey). */
91
- limitKey?: string
92
- /** Permission to READ via the generic data primitives (EntityDef.permission). */
93
- permission?: { feature: string; action: string }
94
- data: {
95
- table: string
96
- schema?: string
97
- tenantScoped?: boolean
98
- tenantIdColumn?: string
99
- searchColumns?: string[]
100
- selectColumns?: string
101
- columnMap?: Record<string, string>
102
- archetype?: string
103
- archetypeKind?: string
104
- filters?: Record<string, string>
105
- }
106
- fields: Array<{
107
- key: string
108
- label: string
109
- type: string
110
- required?: boolean
111
- searchable?: boolean
112
- /** FieldDef.hint — the field's helper text. Carried into the contract
113
- * because it is what tells the agent what a non-obvious column expects:
114
- * an id to look up, a default it should not restate, a format. */
115
- hint?: string
116
- /** Allowed values for select-like fields. */
117
- options?: string[]
118
- /** Table the relation field points at (FieldRelation.table). */
119
- relationTable?: string
120
- }>
121
- }
122
-
123
- /** JSON-safe projection of a PluginAITool + its owning plugin. */
124
- export interface AIToolContract {
125
- id: string
126
- name: string
127
- description: string
128
- mode: 'read' | 'persist'
129
- parameters?: AIToolParameters
130
- permission?: { feature: string; action: string }
131
- limitKey?: string
132
- execution?: AIToolExecution
133
- requiresConfirmation?: boolean
134
- pluginId?: string
135
- category?: string
136
- }
137
-
138
- /** A tenant-scoped pool RPC the server-plane executor may call. Canonical
139
- * definition lives with the plugin types (plugins declare these). */
140
- export type { AgentRpcDeclaration as RpcContract } from '../types/plugins'
141
-
142
- /** Structured domain knowledge rendered into the agent's prompt as labelled
143
- * sections — statuses with transitions, scheduling defaults, free-form rules. */
144
- export interface AgentDomainKnowledge {
145
- statuses?: Record<string, Array<{ id: string; label: string; availableWhen?: unknown }>>
146
- scheduleDefaults?: Record<string, unknown>
147
- businessRules?: Array<{ id: string; description: string }>
148
- }
149
-
150
- export interface AgentContract {
151
- persona?: { name?: string; systemPrompt?: string; icon?: string }
152
- /** When 'server', surfaces stop shipping data-tool executors: the broker
153
- * executes reads/writes server-plane and the surface only handles
154
- * client-plane tools. Flipped per app — no lockstep release. */
155
- executionPlane?: 'client' | 'server'
156
- entities?: EntityContract[]
157
- tools?: AIToolContract[]
158
- registries?: Array<{ pluginId: string; id: string; entityKey?: string; readOnly?: boolean }>
159
- declaredFeatures?: FeatureDeclaration[]
160
- rpcs?: AgentRpcDeclaration[]
161
- domainKnowledge?: AgentDomainKnowledge
162
- }
163
-
164
- export interface AppManifest {
165
- manifestVersion: number
166
- id: string
167
- name: string
168
- backend?: BackendRef
169
- locale?: LocaleConfig
170
- /** Theme tokens — SaasTheme or StorefrontTheme depending on surface; kept
171
- * loose at the core layer to avoid coupling core to surface packages. */
172
- theme?: Record<string, unknown>
173
- /** Keyed by surface name ('admin', 'storefront', …). At least one required. */
174
- surfaces: Record<string, SurfaceManifest>
175
- /** App-defined entity extensions (vertical-specific tables/fields). */
176
- entities?: EntityDef[]
177
- permissions?: PermissionsConfig
178
- billing?: BillingConfig
179
- /** The RESOLVED limit declarations (4-layer merge: core < entity-derived <
180
- * plugin < app), so a server runtime counts exactly what the client counts. */
181
- limitDeclarations?: LimitDeclaration[]
182
- /** v3: the agent contract — see the section comment above. */
183
- agent?: AgentContract
184
- /** sha256 of the canonical serialization (excluding this field). */
185
- contractHash?: string
186
- }
187
-
188
- // ---------------------------------------------------------------------------
189
- // Migration runner — applies sequential migrations from a manifest's version
190
- // up to CURRENT. Each migration is keyed by the version it upgrades FROM.
191
- // ---------------------------------------------------------------------------
192
-
193
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
194
- type AnyManifest = Record<string, any>
195
- export type ManifestMigration = (m: AnyManifest) => AnyManifest
196
-
197
- const migrations = new Map<number, ManifestMigration>()
198
- const supportedBackendProviders: BackendProvider[] = ['supabase', 'fayz-api', 'fayz-shop', 'mock', 'custom']
199
- const supportedPageSections = new Set(['main', 'secondary', 'settings'])
200
- const allowedManifestKeys = new Set([
201
- 'manifestVersion',
202
- 'id',
203
- 'name',
204
- 'backend',
205
- 'locale',
206
- 'theme',
207
- 'permissions',
208
- 'billing',
209
- 'entities',
210
- 'surfaces',
211
- 'limitDeclarations',
212
- 'agent',
213
- 'contractHash',
214
- ])
215
- const allowedBackendKeys = new Set(['provider', 'projectRef', 'url', 'adapterId', 'options'])
216
- const allowedSurfaceKeys = new Set(['scaffold', 'options', 'plugins', 'pages'])
217
- const allowedPluginRefKeys = new Set(['id', 'config', 'enabled'])
218
- const allowedPageKeys = new Set(['path', 'label', 'icon', 'section', 'blocks', 'entity', 'component', 'permission'])
219
- const allowedPermissionKeys = new Set(['feature', 'action'])
220
- const allowedBlockKeys = new Set(['type', 'id', 'props', 'children'])
221
- const manifestIdPattern = /^[a-z0-9][a-z0-9-]*$/
222
-
223
- /** Register a migration that upgrades a manifest FROM `fromVersion` to fromVersion+1. */
224
- export function registerManifestMigration(fromVersion: number, fn: ManifestMigration): void {
225
- migrations.set(fromVersion, fn)
226
- }
227
-
228
- // v2 → v3 is purely additive (limitDeclarations/agent/contractHash are optional):
229
- // a v2 manifest is a valid v3 manifest with those sections absent. Registered
230
- // here so every consumer migrates for free; `fayz manifest emit` fills the new
231
- // sections on the next regeneration.
232
- registerManifestMigration(2, (m) => m)
233
-
234
- /** Bring any manifest up to CURRENT_MANIFEST_VERSION, or throw if it was built
235
- * for a newer SDK than this runtime understands. */
236
- export function migrateManifest(input: AnyManifest): AppManifest {
237
- let m = { ...input }
238
- let version = typeof m.manifestVersion === 'number' ? m.manifestVersion : 1
239
- if (version > CURRENT_MANIFEST_VERSION) {
240
- throw new Error(
241
- `Manifest version ${version} is newer than this SDK supports (${CURRENT_MANIFEST_VERSION}). Upgrade @fayz-ai/core.`,
242
- )
243
- }
244
- while (version < CURRENT_MANIFEST_VERSION) {
245
- const migrate = migrations.get(version)
246
- if (!migrate) {
247
- throw new Error(`No manifest migration registered from version ${version}.`)
248
- }
249
- m = migrate(m)
250
- version += 1
251
- m.manifestVersion = version
252
- }
253
- return m as AppManifest
254
- }
255
-
256
- /** Validate the structural invariants the JSON Schema can't fully express.
257
- * Returns a list of human-readable problems (empty = valid). */
258
- export function validateManifest(m: AppManifest): string[] {
259
- const problems: string[] = []
260
- const manifest = m as unknown as Record<string, unknown>
261
-
262
- addUnsupportedKeys(manifest, allowedManifestKeys, 'manifest', problems)
263
-
264
- if (!Number.isInteger(manifest.manifestVersion)) {
265
- problems.push('manifest.manifestVersion must be an integer')
266
- } else if (manifest.manifestVersion !== CURRENT_MANIFEST_VERSION) {
267
- problems.push(`manifest.manifestVersion must be ${CURRENT_MANIFEST_VERSION}`)
268
- }
269
- if (typeof manifest.id !== 'string' || !manifest.id.trim()) problems.push('manifest.id is required')
270
- else if (!manifestIdPattern.test(manifest.id.trim())) {
271
- problems.push('manifest.id must match /^[a-z0-9][a-z0-9-]*$/')
272
- }
273
- if (typeof manifest.name !== 'string' || !manifest.name.trim()) problems.push('manifest.name is required')
274
-
275
- validateLooseObject(manifest.locale, 'manifest.locale', problems)
276
- validateLooseObject(manifest.theme, 'manifest.theme', problems)
277
- validateLooseObject(manifest.permissions, 'manifest.permissions', problems)
278
- validateLooseObject(manifest.billing, 'manifest.billing', problems)
279
- validateLooseObject(manifest.agent, 'manifest.agent', problems)
280
- if (manifest.contractHash !== undefined && !isNonEmptyString(manifest.contractHash)) {
281
- problems.push('manifest.contractHash must be a non-empty string')
282
- }
283
- if (manifest.limitDeclarations !== undefined) {
284
- if (!Array.isArray(manifest.limitDeclarations)) {
285
- problems.push('manifest.limitDeclarations must be an array')
286
- } else {
287
- manifest.limitDeclarations.forEach((decl, index) => {
288
- if (!isRecord(decl) || !isNonEmptyString(decl.key) || !isNonEmptyString(decl.table)) {
289
- problems.push(`manifest.limitDeclarations[${index}] must declare key and table`)
290
- }
291
- })
292
- }
293
- }
294
- if (manifest.entities !== undefined) {
295
- if (!Array.isArray(manifest.entities)) {
296
- problems.push('manifest.entities must be an array')
297
- } else {
298
- manifest.entities.forEach((entity, index) => {
299
- if (!isRecord(entity)) problems.push(`manifest.entities[${index}] must be an object`)
300
- })
301
- }
302
- }
303
-
304
- if (manifest.backend !== undefined) {
305
- if (!isRecord(manifest.backend)) {
306
- problems.push('manifest.backend must be an object')
307
- } else {
308
- addUnsupportedKeys(manifest.backend, allowedBackendKeys, 'manifest.backend', problems)
309
- const provider = manifest.backend.provider
310
- if (typeof provider !== 'string' || !supportedBackendProviders.includes(provider as BackendProvider)) {
311
- problems.push(`manifest.backend.provider "${String(provider)}" is not supported`)
312
- }
313
- validateOptionalNonEmptyString(manifest.backend.projectRef, 'manifest.backend.projectRef', problems)
314
- validateOptionalNonEmptyString(manifest.backend.url, 'manifest.backend.url', problems)
315
- if (provider === 'custom' && !isNonEmptyString(manifest.backend.adapterId)) {
316
- problems.push('manifest.backend.adapterId is required when provider is "custom"')
317
- } else if (provider !== 'custom') {
318
- validateOptionalNonEmptyString(manifest.backend.adapterId, 'manifest.backend.adapterId', problems)
319
- }
320
- validateLooseObject(manifest.backend.options, 'manifest.backend.options', problems)
321
- }
322
- }
323
-
324
- if (!isRecord(manifest.surfaces) || Object.keys(manifest.surfaces).length === 0) {
325
- problems.push('manifest.surfaces must declare at least one surface')
326
- } else {
327
- for (const [name, surface] of Object.entries(manifest.surfaces)) {
328
- validateSurfaceManifest(name, surface, problems)
329
- }
330
- }
331
-
332
- return problems
333
- }
334
-
335
- function validateSurfaceManifest(name: string, surface: unknown, problems: string[]): void {
336
- if (!isRecord(surface)) {
337
- problems.push(`surface "${name}" must be an object`)
338
- return
339
- }
340
-
341
- addUnsupportedKeys(surface, allowedSurfaceKeys, `surface "${name}"`, problems)
342
- if (!isNonEmptyString(surface.scaffold)) problems.push(`surface "${name}" is missing a scaffold id`)
343
- validateLooseObject(surface.options, `surface "${name}".options`, problems)
344
-
345
- if (surface.pages !== undefined) {
346
- if (!Array.isArray(surface.pages)) {
347
- problems.push(`surface "${name}".pages must be an array`)
348
- } else {
349
- const pagePaths = new Set<string>()
350
- for (const [index, page] of surface.pages.entries()) {
351
- validatePageManifest(name, index, page, pagePaths, problems)
352
- }
353
- }
354
- }
355
-
356
- if (surface.plugins !== undefined) {
357
- if (!Array.isArray(surface.plugins)) {
358
- problems.push(`surface "${name}".plugins must be an array`)
359
- } else {
360
- const pluginIds = new Set<string>()
361
- for (const [index, plugin] of surface.plugins.entries()) {
362
- validatePluginRef(name, index, plugin, pluginIds, problems)
363
- }
364
- }
365
- }
366
- }
367
-
368
- function validatePageManifest(
369
- surfaceName: string,
370
- index: number,
371
- page: unknown,
372
- pagePaths: Set<string>,
373
- problems: string[],
374
- ): void {
375
- const pageLabel = `surface "${surfaceName}" page #${index + 1}`
376
- if (!isRecord(page)) {
377
- problems.push(`${pageLabel} must be an object`)
378
- return
379
- }
380
-
381
- addUnsupportedKeys(page, allowedPageKeys, pageLabel, problems)
382
-
383
- if (!isNonEmptyString(page.path)) {
384
- problems.push(`${pageLabel} has an empty path`)
385
- } else {
386
- const normalizedPath = page.path.trim()
387
- if (pagePaths.has(normalizedPath)) {
388
- problems.push(`surface "${surfaceName}" declares duplicate page path "${normalizedPath}"`)
389
- } else {
390
- pagePaths.add(normalizedPath)
391
- }
392
- }
393
-
394
- if (page.label !== undefined && typeof page.label !== 'string') problems.push(`${pageLabel}.label must be a string`)
395
- if (page.icon !== undefined && typeof page.icon !== 'string') problems.push(`${pageLabel}.icon must be a string`)
396
- if (page.section !== undefined && (typeof page.section !== 'string' || !supportedPageSections.has(page.section))) {
397
- problems.push(`${pageLabel}.section must be one of main, secondary, settings`)
398
- }
399
- if (page.entity !== undefined && !isNonEmptyString(page.entity)) problems.push(`${pageLabel}.entity must be a non-empty string`)
400
- if (page.component !== undefined && !isNonEmptyString(page.component)) {
401
- problems.push(`${pageLabel}.component must be a non-empty string`)
402
- }
403
-
404
- const kinds = [page.blocks, page.entity, page.component].filter((x) => x != null)
405
- if (kinds.length !== 1) {
406
- problems.push(`${pageLabel} must set exactly one of blocks/entity/component`)
407
- }
408
-
409
- if (page.permission !== undefined) {
410
- validatePermission(page.permission, `${pageLabel}.permission`, problems)
411
- }
412
-
413
- if (page.blocks !== undefined) {
414
- if (!Array.isArray(page.blocks)) {
415
- problems.push(`${pageLabel}.blocks must be an array`)
416
- } else {
417
- page.blocks.forEach((block, blockIndex) => {
418
- validateBlockManifest(block, `${pageLabel}.blocks[${blockIndex}]`, problems)
419
- })
420
- }
421
- }
422
- }
423
-
424
- function validatePluginRef(
425
- surfaceName: string,
426
- index: number,
427
- plugin: unknown,
428
- pluginIds: Set<string>,
429
- problems: string[],
430
- ): void {
431
- const pluginLabel = `surface "${surfaceName}" plugin #${index + 1}`
432
- if (!isRecord(plugin)) {
433
- problems.push(`${pluginLabel} must be an object`)
434
- return
435
- }
436
-
437
- addUnsupportedKeys(plugin, allowedPluginRefKeys, pluginLabel, problems)
438
-
439
- if (!isNonEmptyString(plugin.id)) {
440
- problems.push(`${pluginLabel} has an empty id`)
441
- } else {
442
- const normalizedId = plugin.id.trim()
443
- if (pluginIds.has(normalizedId)) {
444
- problems.push(`surface "${surfaceName}" declares duplicate plugin id "${normalizedId}"`)
445
- } else {
446
- pluginIds.add(normalizedId)
447
- }
448
- }
449
-
450
- validateLooseObject(plugin.config, `${pluginLabel}.config`, problems)
451
- if (plugin.enabled !== undefined && typeof plugin.enabled !== 'boolean') {
452
- problems.push(`${pluginLabel}.enabled must be a boolean`)
453
- }
454
- }
455
-
456
- function validatePermission(permission: unknown, path: string, problems: string[]): void {
457
- if (!isRecord(permission)) {
458
- problems.push(`${path} must be an object`)
459
- return
460
- }
461
-
462
- addUnsupportedKeys(permission, allowedPermissionKeys, path, problems)
463
- if (!isNonEmptyString(permission.feature)) problems.push(`${path}.feature is required`)
464
- if (!isNonEmptyString(permission.action)) problems.push(`${path}.action is required`)
465
- }
466
-
467
- function validateBlockManifest(block: unknown, path: string, problems: string[]): void {
468
- if (!isRecord(block)) {
469
- problems.push(`${path} must be an object`)
470
- return
471
- }
472
-
473
- addUnsupportedKeys(block, allowedBlockKeys, path, problems)
474
- if (!isNonEmptyString(block.type)) problems.push(`${path}.type is required`)
475
- validateLooseObject(block.props, `${path}.props`, problems)
476
- if (block.id !== undefined && typeof block.id !== 'string') problems.push(`${path}.id must be a string`)
477
-
478
- if (block.children !== undefined) {
479
- if (!Array.isArray(block.children)) {
480
- problems.push(`${path}.children must be an array`)
481
- } else {
482
- block.children.forEach((child, index) => validateBlockManifest(child, `${path}.children[${index}]`, problems))
483
- }
484
- }
485
- }
486
-
487
- function addUnsupportedKeys(
488
- value: Record<string, unknown>,
489
- allowedKeys: Set<string>,
490
- path: string,
491
- problems: string[],
492
- ): void {
493
- for (const key of Object.keys(value)) {
494
- if (!allowedKeys.has(key)) {
495
- problems.push(`${path}.${key} is not part of AppManifest v${CURRENT_MANIFEST_VERSION}`)
496
- }
497
- }
498
- }
499
-
500
- function validateLooseObject(value: unknown, path: string, problems: string[]): void {
501
- if (value !== undefined && !isRecord(value)) {
502
- problems.push(`${path} must be an object`)
503
- }
504
- }
505
-
506
- function validateOptionalNonEmptyString(value: unknown, path: string, problems: string[]): void {
507
- if (value !== undefined && !isNonEmptyString(value)) {
508
- problems.push(`${path} must be a non-empty string`)
509
- }
510
- }
511
-
512
- function isRecord(value: unknown): value is Record<string, unknown> {
513
- return typeof value === 'object' && value !== null && !Array.isArray(value)
514
- }
515
-
516
- function isNonEmptyString(value: unknown): value is string {
517
- return typeof value === 'string' && value.trim().length > 0
518
- }
@@ -1,52 +0,0 @@
1
- import type { AppManifest } from './index'
2
-
3
- // ---------------------------------------------------------------------------
4
- // Deterministic serialization — the SAME manifest object must always produce
5
- // the SAME bytes, because the sha256 of those bytes (`contractHash`) is what
6
- // makes `fayz manifest sync` idempotent and lets `fayz doctor` prove the
7
- // committed app.manifest.json is not stale. Rules: object keys sorted, arrays
8
- // kept in declared order (order is meaning: nav position, migration order),
9
- // 2-space indent, trailing newline, `contractHash` itself excluded.
10
- // ---------------------------------------------------------------------------
11
-
12
- function sortValue(value: unknown): unknown {
13
- if (Array.isArray(value)) return value.map(sortValue)
14
- if (value !== null && typeof value === 'object') {
15
- const record = value as Record<string, unknown>
16
- const out: Record<string, unknown> = {}
17
- for (const key of Object.keys(record).sort()) {
18
- const v = record[key]
19
- if (v !== undefined) out[key] = sortValue(v)
20
- }
21
- return out
22
- }
23
- return value
24
- }
25
-
26
- /** Canonical JSON of a manifest (contractHash excluded, keys sorted). */
27
- export function serializeManifest(manifest: AppManifest): string {
28
- const { contractHash: _omit, ...rest } = manifest
29
- return `${JSON.stringify(sortValue(rest), null, 2)}\n`
30
- }
31
-
32
- /**
33
- * sha256 (hex) of the canonical serialization. Async because it runs on
34
- * WebCrypto (`globalThis.crypto.subtle`), which exists in every runtime we
35
- * target (browsers, Node ≥ 18, edge) — no node:crypto import, keeping this
36
- * module bundleable everywhere.
37
- */
38
- export async function computeContractHash(manifest: AppManifest): Promise<string> {
39
- const bytes = new TextEncoder().encode(serializeManifest(manifest))
40
- const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes)
41
- return Array.from(new Uint8Array(digest))
42
- .map((b) => b.toString(16).padStart(2, '0'))
43
- .join('')
44
- }
45
-
46
- /** Serialize WITH the freshly computed contractHash embedded — what
47
- * `fayz manifest emit` writes to app.manifest.json. */
48
- export async function serializeManifestWithHash(manifest: AppManifest): Promise<string> {
49
- const contractHash = await computeContractHash(manifest)
50
- const { contractHash: _omit, ...rest } = manifest
51
- return `${JSON.stringify(sortValue({ ...rest, contractHash }), null, 2)}\n`
52
- }
@@ -1,85 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Reusable phone / country helpers (SDK-level — shared by any plugin that
3
- // collects a phone number: booking, CRM, forms, auth…). Provides the country
4
- // list (flag + dial code + national mask) and mask/unmask utilities.
5
- // ---------------------------------------------------------------------------
6
-
7
- export interface CountryDef {
8
- /** ISO 3166-1 alpha-2 code, e.g. 'BR'. */
9
- iso2: string
10
- /** Localized-ish display name. */
11
- name: string
12
- /** International dial code including '+', e.g. '+55'. */
13
- dial: string
14
- /** Flag emoji (derived from iso2). */
15
- flag: string
16
- /** National number mask; '#' = one digit. e.g. '(##) #####-####'. */
17
- mask: string
18
- }
19
-
20
- /** Derive a flag emoji from an ISO2 code via regional indicator symbols. */
21
- function flagOf(iso2: string): string {
22
- return iso2
23
- .toUpperCase()
24
- .replace(/[^A-Z]/g, '')
25
- .split('')
26
- .map((c) => String.fromCodePoint(0x1f1e6 + c.charCodeAt(0) - 65))
27
- .join('')
28
- }
29
-
30
- const RAW: Omit<CountryDef, 'flag'>[] = [
31
- { iso2: 'BR', name: 'Brasil', dial: '+55', mask: '(##) #####-####' },
32
- { iso2: 'US', name: 'Estados Unidos', dial: '+1', mask: '(###) ###-####' },
33
- { iso2: 'PT', name: 'Portugal', dial: '+351', mask: '### ### ###' },
34
- { iso2: 'AR', name: 'Argentina', dial: '+54', mask: '(##) ####-####' },
35
- { iso2: 'ES', name: 'Espanha', dial: '+34', mask: '### ### ###' },
36
- { iso2: 'MX', name: 'México', dial: '+52', mask: '## #### ####' },
37
- { iso2: 'GB', name: 'Reino Unido', dial: '+44', mask: '##### ######' },
38
- { iso2: 'FR', name: 'França', dial: '+33', mask: '# ## ## ## ##' },
39
- { iso2: 'DE', name: 'Alemanha', dial: '+49', mask: '#### #######' },
40
- { iso2: 'IT', name: 'Itália', dial: '+39', mask: '### #######' },
41
- ]
42
-
43
- export const COUNTRIES: CountryDef[] = RAW.map((c) => ({ ...c, flag: flagOf(c.iso2) }))
44
-
45
- export const DEFAULT_COUNTRY = 'BR'
46
-
47
- const BY_ISO = new Map(COUNTRIES.map((c) => [c.iso2, c]))
48
-
49
- /** Look up a country by ISO2 (falls back to the default). */
50
- export function getCountry(iso2: string | undefined): CountryDef {
51
- return (iso2 && BY_ISO.get(iso2.toUpperCase())) || BY_ISO.get(DEFAULT_COUNTRY)!
52
- }
53
-
54
- /** Keep only digit characters. */
55
- export function unmaskPhone(value: string): string {
56
- return (value || '').replace(/\D/g, '')
57
- }
58
-
59
- /**
60
- * Apply a '#'-placeholder mask to a raw string, consuming its digits. Extra
61
- * digits beyond the mask are dropped; partial input masks progressively.
62
- *
63
- * maskPhone('11987654321', '(##) #####-####') -> '(11) 98765-4321'
64
- */
65
- export function maskPhone(value: string, mask: string): string {
66
- const digits = unmaskPhone(value)
67
- let out = ''
68
- let di = 0
69
- for (const ch of mask) {
70
- if (di >= digits.length) break
71
- if (ch === '#') {
72
- out += digits[di++]
73
- } else {
74
- out += ch
75
- }
76
- }
77
- return out
78
- }
79
-
80
- /** Count how many digits a mask expects. */
81
- export function maskDigitCount(mask: string): number {
82
- let n = 0
83
- for (const ch of mask) if (ch === '#') n++
84
- return n
85
- }
@@ -1,30 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { getSupabaseClientOptional } from '../data/supabase'
3
-
4
- /**
5
- * createSafeDataProvider — the standard lazy provider resolver every plugin
6
- * repeated by hand. Returns a Proxy that, on first method call, resolves to the
7
- * Supabase-backed provider when a Supabase client is configured, otherwise the
8
- * mock provider. Resolution is deferred so importing a plugin never forces a
9
- * Supabase connection, and memoized so it only happens once.
10
- *
11
- * const provider = options?.dataProvider ?? createSafeDataProvider(
12
- * () => createSupabaseCrmProvider(providerOptions),
13
- * () => createMockCrmProvider(),
14
- * )
15
- */
16
- export function createSafeDataProvider<T extends object>(
17
- makeSupabase: () => T,
18
- makeMock: () => T,
19
- ): T {
20
- let resolved: T | null = null
21
- const get = (): T => {
22
- if (!resolved) {
23
- resolved = getSupabaseClientOptional() ? makeSupabase() : makeMock()
24
- }
25
- return resolved
26
- }
27
- return new Proxy({} as T, {
28
- get: (_target, prop) => (...args: any[]) => (get() as any)[prop](...args),
29
- })
30
- }
@@ -1 +0,0 @@
1
- export { definePlugin, resolvePluginRuntime, getWidgetsForZone, PluginRuntimeProvider, usePluginRuntime, usePluginRuntimeOptional } from './runtime'