@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 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhD,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAUD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAsD3E;AAMD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAiBlE"}
@@ -1,33 +0,0 @@
1
- import type { DenyReason } from './engine'
2
-
3
- // ---------------------------------------------------------------------------
4
- // The structured denial an AGENT surface returns instead of failing silently
5
- // (docs/ENTITLEMENTS.md § "Superfície de agentes de IA"). One shape for every
6
- // executor — FAB client-plane, the Fayz broker, and the pool RPCs (which emit
7
- // its jsonb mirror in their `denial` field) — so the model can explain the
8
- // denial and offer the upgrade path conversationally.
9
- // ---------------------------------------------------------------------------
10
-
11
- /** Where a denied tenant goes to upgrade — the conversational UpgradeModal. */
12
- export const UPGRADE_URL = '/settings/subscription'
13
-
14
- export interface AgentDenialLimit {
15
- key: string
16
- max: number
17
- used: number
18
- }
19
-
20
- export interface AgentDenial {
21
- allowed: false
22
- reason: DenyReason
23
- /** Present when `reason === 'limit'`. */
24
- limit?: AgentDenialLimit
25
- upgradeUrl: string
26
- }
27
-
28
- export type AgentGuardResult = { allowed: true } | AgentDenial
29
-
30
- /** Build a denial with the canonical upgrade URL attached. */
31
- export function agentDenial(reason: DenyReason, limit?: AgentDenialLimit): AgentDenial {
32
- return { allowed: false, reason, ...(limit ? { limit } : {}), upgradeUrl: UPGRADE_URL }
33
- }
@@ -1,48 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import type { PermissionProfile } from '../types/permissions'
3
- import { profileHasPermission, isOwnerProfile, resolveAccess } from './engine'
4
- import { agentDenial, UPGRADE_URL } from './denial'
5
-
6
- // The composition matrix (role × plan × limit math) is covered by
7
- // packages/saas/src/access/resolver.test.ts, which now runs against this
8
- // engine through the saas re-export — parity by construction. Here we cover
9
- // the pieces that are new or only reachable from core.
10
-
11
- const staff: PermissionProfile = { id: 'staff', name: 'Staff', grants: { clients: ['read'] } }
12
-
13
- describe('profileHasPermission', () => {
14
- it('null profile is allow-all (catalog not hydrated yet)', () => {
15
- expect(profileHasPermission(null, 'anything', 'delete')).toBe(true)
16
- })
17
-
18
- it('owner matches by id or (localized) name', () => {
19
- expect(isOwnerProfile({ id: 'owner', name: 'Proprietário', grants: {} })).toBe(true)
20
- expect(isOwnerProfile({ id: 'x', name: 'Owner', grants: {} })).toBe(true)
21
- expect(isOwnerProfile(staff)).toBe(false)
22
- })
23
-
24
- it('manage satisfies any action; empty grant list denies actionless check', () => {
25
- const manager: PermissionProfile = { id: 'm', name: 'M', grants: { clients: ['manage'], reports: [] } }
26
- expect(profileHasPermission(manager, 'clients', 'delete')).toBe(true)
27
- expect(profileHasPermission(manager, 'reports')).toBe(false)
28
- })
29
- })
30
-
31
- describe('agentDenial', () => {
32
- it('carries the canonical upgrade URL', () => {
33
- expect(agentDenial('plan')).toEqual({ allowed: false, reason: 'plan', upgradeUrl: UPGRADE_URL })
34
- })
35
-
36
- it('carries limit details for limit denials', () => {
37
- const d = agentDenial('limit', { key: 'clients', max: 100, used: 100 })
38
- expect(d.limit).toEqual({ key: 'clients', max: 100, used: 100 })
39
- expect(d.reason).toBe('limit')
40
- })
41
-
42
- it('shape matches what resolveAccess produces for the same reason', () => {
43
- const decision = resolveAccess({ profile: staff, plan: null }, 'marketing')
44
- expect(decision.allowed).toBe(false)
45
- const d = agentDenial(decision.reason!)
46
- expect(d.reason).toBe('role')
47
- })
48
- })
@@ -1,136 +0,0 @@
1
- import type { PermissionProfile, PermissionAction, SystemPermission } from '../types/permissions'
2
- import type { Plan } from '../types/billing'
3
-
4
- // ---------------------------------------------------------------------------
5
- // The access engine — the ONE implementation of the role × plan × limit
6
- // decision, shared by every runtime that gates anything:
7
- //
8
- // - browser (saas AccessProvider / PermissionsProvider re-export from here)
9
- // - headless client-plane (saas access/headless.ts)
10
- // - the Fayz broker (server-side agent authorization imports this package)
11
- //
12
- // Hard constraint: this module (and everything under core/src/access) must stay
13
- // free of React, zustand and supabase — pure functions over plain data — so the
14
- // exact same code runs in Node/edge. Guarded by scripts/check-access-purity.mjs.
15
- // ---------------------------------------------------------------------------
16
-
17
- /** Why an access request was denied. `limit` only occurs on guarded writes. */
18
- export type DenyReason = 'role' | 'plan' | 'limit'
19
-
20
- export interface AccessDecision {
21
- allowed: boolean
22
- reason?: DenyReason
23
- }
24
-
25
- /** The minimal subject a decision needs — a projection of any richer session. */
26
- export interface AccessSubject {
27
- /** Effective RBAC profile — the previewed role during impersonation. */
28
- profile: PermissionProfile | null
29
- /** The tenant's currently-active plan. */
30
- plan: Plan | null
31
- }
32
-
33
- /**
34
- * The org owner has every permission implicitly. Owner is represented by the
35
- * canonical role id `owner` across adapters (see org/adapters/supabase.ts
36
- * buildPermissionProfiles, which also grants owner all catalog perms, and the
37
- * mock adapter's `owner` fallback). We match by id first, falling back to the
38
- * (possibly localized) name for safety.
39
- */
40
- export function isOwnerProfile(profile: PermissionProfile): boolean {
41
- return profile.id === 'owner' || profile.name?.toLowerCase() === 'owner'
42
- }
43
-
44
- /**
45
- * The single role-side permission check. The plan axis composes ON TOP of it in
46
- * {@link resolveAccess} instead of reimplementing owner-bypass / `manage`
47
- * semantics.
48
- */
49
- export function profileHasPermission(
50
- profile: PermissionProfile | null,
51
- feature: string,
52
- action?: string,
53
- ): boolean {
54
- if (!profile) return true
55
-
56
- // Owner bypass: never lock the owner out, even if the RBAC catalog is empty
57
- // or still loading. Because permission checks read the *effective* profile,
58
- // an owner previewing a non-owner role correctly loses this bypass — the
59
- // preview stays honest.
60
- if (isOwnerProfile(profile)) return true
61
-
62
- const actions = profile.grants[feature]
63
- if (!actions) return false
64
-
65
- if (!action) return actions.length > 0
66
-
67
- return actions.includes(action) || actions.includes('manage')
68
- }
69
-
70
- /**
71
- * The org-management side of a profile: managing team, billing, settings and
72
- * permissions themselves. Separate axis from `grants` — these gate the settings
73
- * tabs, not a feature's rows — and it follows the same owner bypass, so the one
74
- * person who can hand out access is never locked out of doing it.
75
- */
76
- export function profileHasSystemPermission(
77
- profile: PermissionProfile | null,
78
- permission: SystemPermission,
79
- ): boolean {
80
- if (!profile) return true
81
- if (isOwnerProfile(profile)) return true
82
- return profile.systemPermissions?.includes(permission) ?? false
83
- }
84
-
85
- /**
86
- * Does the current plan entitle `feature`? A plan gates a feature ONLY with an
87
- * explicit `false`; absent means "not gated by this plan" (allowed). This keeps
88
- * entitlements additive and safe: a feature the plan has never heard of stays
89
- * open rather than silently locking.
90
- */
91
- export function isEntitledByPlan(plan: Plan | null, feature: string): boolean {
92
- return plan?.entitlements?.features?.[feature] !== false
93
- }
94
-
95
- /**
96
- * The single access decision: role FIRST, then plan.
97
- *
98
- * 1. Role — reuses `profileHasPermission` (owner bypass + `manage` semantics
99
- * live there, and impersonation composes for free because `profile` is the
100
- * previewed role). Denied → `{ allowed:false, reason:'role' }`.
101
- * 2. Plan — even the owner does NOT bypass the plan: upgrading is the tenant's
102
- * decision, not a role power. Denied → `{ allowed:false, reason:'plan' }`.
103
- */
104
- export function resolveAccess(
105
- subject: AccessSubject,
106
- feature: string,
107
- action?: PermissionAction,
108
- ): AccessDecision {
109
- if (!profileHasPermission(subject.profile, feature, action)) {
110
- return { allowed: false, reason: 'role' }
111
- }
112
- if (!isEntitledByPlan(subject.plan, feature)) {
113
- return { allowed: false, reason: 'plan' }
114
- }
115
- return { allowed: true }
116
- }
117
-
118
- /**
119
- * Pure numeric side of a limit: given the plan cap, whether a countable
120
- * declaration exists, and the current usage, derive the limit math. Unlimited
121
- * when there's no declaration to count, no cap on the plan, or the cap is `-1`.
122
- */
123
- export function resolveLimit(params: { cap: number | undefined; hasDeclaration: boolean; used: number }): {
124
- max: number
125
- used: number
126
- remaining: number
127
- atLimit: boolean
128
- unlimited: boolean
129
- } {
130
- const { cap, hasDeclaration, used } = params
131
- const unlimited = !hasDeclaration || cap === undefined || cap === -1
132
- const max = unlimited ? Infinity : cap
133
- const remaining = unlimited ? Infinity : Math.max(0, max - used)
134
- const atLimit = !unlimited && used >= max
135
- return { max, used, remaining, atLimit, unlimited }
136
- }
@@ -1,14 +0,0 @@
1
- export {
2
- isOwnerProfile,
3
- profileHasPermission,
4
- profileHasSystemPermission,
5
- isEntitledByPlan,
6
- resolveAccess,
7
- resolveLimit,
8
- } from './engine'
9
- export type { DenyReason, AccessDecision, AccessSubject } from './engine'
10
-
11
- export { UPGRADE_URL, agentDenial } from './denial'
12
- export type { AgentDenial, AgentDenialLimit, AgentGuardResult } from './denial'
13
-
14
- export { mergeLimitDeclarations } from './limits'
@@ -1,18 +0,0 @@
1
- import type { LimitDeclaration } from '../types/entitlements'
2
-
3
- /**
4
- * The 4-layer limit-declaration merge, extracted so the browser AccessProvider
5
- * and the manifest derivation (`fayz manifest emit`) resolve the EXACT same
6
- * final set: core built-ins < entity-derived < plugin `declaredLimits` < app
7
- * overrides. Later layers win by key. Pure: pass the layers in, get the merge
8
- * out — deriving a declaration FROM an entity stays at the call site.
9
- */
10
- export function mergeLimitDeclarations(
11
- ...layers: Array<LimitDeclaration[] | undefined>
12
- ): LimitDeclaration[] {
13
- const byKey = new Map<string, LimitDeclaration>()
14
- for (const layer of layers) {
15
- for (const decl of layer ?? []) byKey.set(decl.key, decl)
16
- }
17
- return Array.from(byKey.values())
18
- }
@@ -1,188 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Postal code → address lookup (SDK-level — shared by any surface that collects
3
- // an address: storefront checkout, the SaaS address book, CRM, booking).
4
- //
5
- // Sits beside ./phone for the same reason: knowing that a Brazilian CEP is
6
- // eight digits, and that '01310100' means Avenida Paulista, is not a
7
- // storefront concern. It belongs wherever an address is typed.
8
- //
9
- // The lookup itself is behind an interface. ViaCEP is the default because it is
10
- // free, keyless and CORS-enabled, but it is a third party the merchant does not
11
- // control — so swapping it for an edge-function proxy (shared cache, no
12
- // third-party call from the shopper's browser) must be one line, not a rewrite.
13
- // ---------------------------------------------------------------------------
14
-
15
- /**
16
- * An address as a postal service knows it: everything except the parts only the
17
- * resident can supply (number, complement).
18
- *
19
- * Field names mirror ShippingAddressInput in @fayz-ai/shop so the value crosses
20
- * from lookup to checkout to the order with no translation step in between.
21
- */
22
- export interface PostalAddress {
23
- postalCode: string
24
- street: string
25
- district: string
26
- city: string
27
- state: string
28
- country: string
29
- /** IBGE municipality code when the source provides one — useful for tax later. */
30
- cityCode?: string
31
- }
32
-
33
- /** Where a postal code is resolved. Implementations must not throw for a
34
- * well-formed code that simply does not exist — that is `null`, not an error. */
35
- export interface PostalLookupProvider {
36
- lookup(postalCode: string): Promise<PostalAddress | null>
37
- }
38
-
39
- /** Keep only digits. */
40
- export function normalizePostalCode(value: string): string {
41
- return (value || '').replace(/\D/g, '')
42
- }
43
-
44
- /** '01310100' | '01310-100' -> '01310-100'. Partial input formats progressively. */
45
- export function formatPostalCode(value: string): string {
46
- const digits = normalizePostalCode(value).slice(0, 8)
47
- return digits.length > 5 ? `${digits.slice(0, 5)}-${digits.slice(5)}` : digits
48
- }
49
-
50
- /** A Brazilian CEP is exactly eight digits. */
51
- export function isValidPostalCode(value: string): boolean {
52
- return normalizePostalCode(value).length === 8
53
- }
54
-
55
- // ---------------------------------------------------------------------------
56
- // Cache
57
- //
58
- // A CEP maps to the same street for years, so the second lookup of one should
59
- // never leave the browser. This also keeps a busy storefront well clear of
60
- // ViaCEP's per-IP rate limit. localStorage when there is a window, an in-memory
61
- // Map otherwise (SSR, tests, node) — never a hard failure over a cache miss.
62
- // ---------------------------------------------------------------------------
63
-
64
- const CACHE_KEY = 'fayz.postal.cache.v1'
65
- const memoryCache = new Map<string, PostalAddress>()
66
-
67
- function readCache(code: string): PostalAddress | undefined {
68
- const hit = memoryCache.get(code)
69
- if (hit) return hit
70
- try {
71
- const raw = globalThis.localStorage?.getItem(CACHE_KEY)
72
- if (!raw) return undefined
73
- const all = JSON.parse(raw) as Record<string, PostalAddress>
74
- const stored = all[code]
75
- if (stored) memoryCache.set(code, stored)
76
- return stored
77
- } catch {
78
- return undefined
79
- }
80
- }
81
-
82
- function writeCache(code: string, address: PostalAddress): void {
83
- memoryCache.set(code, address)
84
- try {
85
- const store = globalThis.localStorage
86
- if (!store) return
87
- const raw = store.getItem(CACHE_KEY)
88
- const all = raw ? (JSON.parse(raw) as Record<string, PostalAddress>) : {}
89
- all[code] = address
90
- store.setItem(CACHE_KEY, JSON.stringify(all))
91
- } catch {
92
- /* quota or private mode — the in-memory copy still stands for this session */
93
- }
94
- }
95
-
96
- /** Drop the cache. Exposed for tests and for a "wrong address?" escape hatch. */
97
- export function clearPostalCache(): void {
98
- memoryCache.clear()
99
- try {
100
- globalThis.localStorage?.removeItem(CACHE_KEY)
101
- } catch {
102
- /* ignore */
103
- }
104
- }
105
-
106
- // ---------------------------------------------------------------------------
107
- // ViaCEP
108
- // ---------------------------------------------------------------------------
109
-
110
- interface ViaCepResponse {
111
- cep?: string
112
- logradouro?: string
113
- bairro?: string
114
- localidade?: string
115
- uf?: string
116
- ibge?: string
117
- erro?: boolean | string
118
- }
119
-
120
- /**
121
- * ViaCEP (https://viacep.com.br). Free, no key, CORS-enabled.
122
- *
123
- * The trap: an unknown CEP does NOT come back as 404. ViaCEP answers HTTP 200
124
- * with `{"erro": true}` (and, on some deploys, the string "true"), so checking
125
- * response.ok alone yields an address of empty strings that silently overwrites
126
- * whatever the shopper had typed. Both shapes are treated as "not found".
127
- */
128
- export function createViaCepProvider(options?: { fetcher?: typeof fetch }): PostalLookupProvider {
129
- const fetcher = options?.fetcher ?? globalThis.fetch
130
- return {
131
- async lookup(postalCode: string): Promise<PostalAddress | null> {
132
- const code = normalizePostalCode(postalCode)
133
- if (code.length !== 8) return null
134
-
135
- const response = await fetcher(`https://viacep.com.br/ws/${code}/json/`)
136
- if (!response.ok) throw new Error(`Consulta de CEP falhou (${response.status})`)
137
-
138
- const data = (await response.json()) as ViaCepResponse
139
- if (data.erro === true || data.erro === 'true' || !data.localidade) return null
140
-
141
- return {
142
- postalCode: code,
143
- street: data.logradouro ?? '',
144
- district: data.bairro ?? '',
145
- city: data.localidade,
146
- state: (data.uf ?? '').toUpperCase(),
147
- country: 'BR',
148
- cityCode: data.ibge || undefined,
149
- }
150
- },
151
- }
152
- }
153
-
154
- // ---------------------------------------------------------------------------
155
- // Module-level resolver — same shape as setShopTenantResolver /
156
- // setShopAccessTokenResolver, so an app overrides the source once at boot
157
- // instead of threading a provider through every component that needs a CEP.
158
- // ---------------------------------------------------------------------------
159
-
160
- let _provider: PostalLookupProvider | null = null
161
-
162
- export function setPostalLookupProvider(provider: PostalLookupProvider | null): void {
163
- _provider = provider
164
- }
165
-
166
- export function getPostalLookupProvider(): PostalLookupProvider {
167
- if (!_provider) _provider = createViaCepProvider()
168
- return _provider
169
- }
170
-
171
- /**
172
- * Resolve a postal code to an address, through the cache.
173
- *
174
- * Returns null for a code that is malformed or does not exist; throws only when
175
- * the lookup itself failed (offline, provider down), so a caller can tell
176
- * "no such CEP" from "we could not check right now" and say the right thing.
177
- */
178
- export async function lookupPostalCode(postalCode: string): Promise<PostalAddress | null> {
179
- const code = normalizePostalCode(postalCode)
180
- if (code.length !== 8) return null
181
-
182
- const cached = readCache(code)
183
- if (cached) return cached
184
-
185
- const address = await getPostalLookupProvider().lookup(code)
186
- if (address) writeCache(code, address)
187
- return address
188
- }
@@ -1,69 +0,0 @@
1
- import * as React from 'react'
2
- import { getScaffold } from '../registry'
3
- import { migrateManifest, validateManifest, CURRENT_MANIFEST_VERSION } from '../manifest'
4
- import type { AppManifest } from '../manifest'
5
-
6
- // ---------------------------------------------------------------------------
7
- // defineApp / renderApp — the entry points that turn an AppManifest into a
8
- // running app. Scaffolds (admin, storefront, portal) self-register into the
9
- // scaffold registry when their package is imported, so core never depends on
10
- // them: a generated app imports the scaffold package(s) it needs, then calls
11
- // renderApp(manifest). createSaasApp / createStorefrontApp become sugar that
12
- // builds a manifest and hands it here.
13
- // ---------------------------------------------------------------------------
14
-
15
- const ManifestContext = React.createContext<AppManifest | null>(null)
16
-
17
- /** Access the active app manifest from any component under renderApp. */
18
- export function useManifest(): AppManifest | null {
19
- return React.useContext(ManifestContext)
20
- }
21
-
22
- /** Stamp + validate a manifest authored in code (the sugar path). */
23
- export function defineApp(
24
- manifest: Omit<AppManifest, 'manifestVersion'> & { manifestVersion?: number },
25
- ): AppManifest {
26
- const full = {
27
- ...manifest,
28
- manifestVersion: manifest.manifestVersion ?? CURRENT_MANIFEST_VERSION,
29
- } as AppManifest
30
- const problems = validateManifest(full)
31
- if (problems.length) {
32
- throw new Error(`Invalid AppManifest:\n - ${problems.join('\n - ')}`)
33
- }
34
- return full
35
- }
36
-
37
- export interface RenderAppOptions {
38
- /** Which surface to mount; defaults to the first declared surface. */
39
- surface?: string
40
- }
41
-
42
- /** Resolve, migrate and validate a manifest, then mount the scaffold for the
43
- * chosen surface. Accepts a raw object (e.g. parsed app.manifest.json). */
44
- export function renderApp(
45
- input: AppManifest | Record<string, unknown>,
46
- opts: RenderAppOptions = {},
47
- ): React.ReactElement {
48
- const manifest = migrateManifest(input as Record<string, unknown>)
49
- const problems = validateManifest(manifest)
50
- if (problems.length) {
51
- throw new Error(`Invalid AppManifest:\n - ${problems.join('\n - ')}`)
52
- }
53
- const surfaceName = opts.surface ?? Object.keys(manifest.surfaces)[0]
54
- const surface = surfaceName ? manifest.surfaces[surfaceName] : undefined
55
- if (!surface) {
56
- throw new Error(`Surface "${surfaceName}" not found in manifest.`)
57
- }
58
- const Scaffold = getScaffold(surface.scaffold)
59
- if (!Scaffold) {
60
- throw new Error(
61
- `Scaffold "${surface.scaffold}" is not registered. Call renderApp from the package that provides it (e.g. @fayz-ai/saas, @fayz-ai/portal, @fayz-ai/storefront) or call its register*Scaffold() first.`,
62
- )
63
- }
64
- return React.createElement(
65
- ManifestContext.Provider,
66
- { value: manifest },
67
- React.createElement(Scaffold, { manifest, surface: surfaceName }),
68
- )
69
- }
@@ -1,68 +0,0 @@
1
- import * as React from 'react'
2
- import { getBlock } from '../registry'
3
-
4
- // ---------------------------------------------------------------------------
5
- // Block tree — the universal page primitive. A page (admin or storefront) is an
6
- // ordered tree of typed blocks. Block `type`s are resolved against the block
7
- // registry, so a page is pure data: `{ type, props, children }`. This
8
- // generalizes the storefront's HomeSection switch to every surface.
9
- // ---------------------------------------------------------------------------
10
-
11
- export interface BlockNode {
12
- /** Registered block id, e.g. 'hero', 'products', 'kpi-grid', 'custom:foo'. */
13
- type: string
14
- /** Stable id for keys / editing; optional. */
15
- id?: string
16
- props?: Record<string, unknown>
17
- children?: BlockNode[]
18
- }
19
-
20
- /** A block component receives its declared props plus the raw node (so a block
21
- * can render its own children via <BlockChildren>). */
22
- export type BlockComponentProps = Record<string, unknown> & {
23
- node?: BlockNode
24
- children?: React.ReactNode
25
- }
26
-
27
- // Rendered in place of an unresolved block type. Unknown blocks are caught
28
- // ahead of production by `fayz doctor` / the conformance kit, so this visible
29
- // marker is a development aid rather than a production concern.
30
- function UnknownBlock({ type }: { type: string }): React.ReactElement | null {
31
- return React.createElement(
32
- 'div',
33
- {
34
- style: {
35
- border: '1px dashed #c026d3',
36
- color: '#a21caf',
37
- padding: '8px 12px',
38
- font: '12px ui-monospace, monospace',
39
- borderRadius: 6,
40
- },
41
- },
42
- `Unknown block: "${type}"`,
43
- )
44
- }
45
-
46
- export function BlockRenderer({ node }: { node: BlockNode }): React.ReactElement | null {
47
- const Comp = getBlock(node.type)
48
- const childEls = node.children?.length
49
- ? node.children.map((child, i) =>
50
- React.createElement(BlockRenderer, { key: child.id ?? `${child.type}-${i}`, node: child }),
51
- )
52
- : null
53
- if (!Comp) return React.createElement(UnknownBlock, { type: node.type })
54
- return React.createElement(Comp, { ...(node.props ?? {}), node }, childEls)
55
- }
56
-
57
- /** Render an ordered list of blocks (a page body). */
58
- export function renderBlocks(nodes: BlockNode[]): React.ReactNode {
59
- return nodes.map((node, i) =>
60
- React.createElement(BlockRenderer, { key: node.id ?? `${node.type}-${i}`, node }),
61
- )
62
- }
63
-
64
- /** Convenience for block components that render their own children. */
65
- export function BlockChildren({ node }: { node?: BlockNode }): React.ReactNode {
66
- if (!node?.children?.length) return null
67
- return renderBlocks(node.children)
68
- }