@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.
- package/README.md +2 -2
- package/dist/access/index.js.map +1 -1
- package/dist/{chunk-L4TLMYGC.js → chunk-6RBW2Y5K.js} +3 -3
- package/dist/chunk-6RBW2Y5K.js.map +1 -0
- package/dist/chunk-732GZEUI.js +1592 -0
- package/dist/chunk-732GZEUI.js.map +1 -0
- package/dist/{chunk-X5THHG6T.js → chunk-CPMHCTX3.js} +3 -3
- package/dist/chunk-CPMHCTX3.js.map +1 -0
- package/dist/{chunk-V2MSAUHO.js → chunk-JO7Z2AOE.js} +2 -2
- package/dist/chunk-JO7Z2AOE.js.map +1 -0
- package/dist/{chunk-5RJVUC6J.js → chunk-M27JPX3K.js} +12 -3
- package/dist/chunk-M27JPX3K.js.map +1 -0
- package/dist/chunk-OXQGRJZT.js +125 -0
- package/dist/chunk-OXQGRJZT.js.map +1 -0
- package/dist/data/backend-env.d.ts +38 -0
- package/dist/data/backend-env.d.ts.map +1 -0
- package/dist/data/index.js +1 -1
- package/dist/entity/events.d.ts +14 -0
- package/dist/entity/events.d.ts.map +1 -0
- package/dist/events/envelope.d.ts +31 -0
- package/dist/events/envelope.d.ts.map +1 -0
- package/dist/events/index.d.ts +4 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/log.d.ts +44 -0
- package/dist/events/log.d.ts.map +1 -0
- package/dist/events/types.d.ts +40 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/i18n/index.d.ts +10 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/index.d.ts +27 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +388 -52
- package/dist/index.js.map +1 -1
- package/dist/integrations/index.js +1 -1
- package/dist/{integrations/index.d.ts → integrations.d.ts} +2 -2
- package/dist/integrations.d.ts.map +1 -0
- package/dist/{address/index.d.ts → lib/address.d.ts} +1 -1
- package/dist/{address/index.d.ts.map → lib/address.d.ts.map} +1 -1
- package/dist/{phone/index.d.ts → lib/phone.d.ts} +1 -1
- package/dist/lib/phone.d.ts.map +1 -0
- package/dist/{seo/index.d.ts → lib/seo.d.ts} +18 -1
- package/dist/lib/seo.d.ts.map +1 -0
- package/dist/{tenant/index.d.ts → lib/tenant.d.ts} +1 -1
- package/dist/lib/tenant.d.ts.map +1 -0
- package/dist/manifest/index.d.ts +1 -1
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/messaging/index.d.ts +3 -0
- package/dist/messaging/index.d.ts.map +1 -0
- package/dist/messaging/registry.d.ts +35 -0
- package/dist/messaging/registry.d.ts.map +1 -0
- package/dist/messaging/types.d.ts +67 -0
- package/dist/messaging/types.d.ts.map +1 -0
- package/dist/plugin/index.js +2 -1
- package/dist/plugin/runtime.d.ts +17 -0
- package/dist/plugin/runtime.d.ts.map +1 -1
- package/dist/registry/block-catalog.d.ts +78 -0
- package/dist/registry/block-catalog.d.ts.map +1 -0
- package/dist/registry/block-constraints.d.ts +64 -0
- package/dist/registry/block-constraints.d.ts.map +1 -0
- package/dist/registry/block-schema.d.ts +69 -0
- package/dist/registry/block-schema.d.ts.map +1 -0
- package/dist/registry/blocks.d.ts +84 -0
- package/dist/registry/blocks.d.ts.map +1 -0
- package/dist/registry/entities.d.ts +153 -0
- package/dist/registry/entities.d.ts.map +1 -0
- package/dist/registry/extension-points.d.ts +117 -0
- package/dist/registry/extension-points.d.ts.map +1 -0
- package/dist/registry/handles.d.ts +41 -0
- package/dist/registry/handles.d.ts.map +1 -0
- package/dist/registry/index.d.ts +41 -41
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/loaders.d.ts +55 -0
- package/dist/registry/loaders.d.ts.map +1 -0
- package/dist/registry/registry-class.d.ts +157 -0
- package/dist/registry/registry-class.d.ts.map +1 -0
- package/dist/registry/schema-check.d.ts +14 -0
- package/dist/registry/schema-check.d.ts.map +1 -0
- package/dist/router/index.d.ts +78 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/{testing/index.d.ts → testing.d.ts} +3 -3
- package/dist/testing.d.ts.map +1 -0
- package/dist/types/analytics.d.ts +196 -0
- package/dist/types/analytics.d.ts.map +1 -0
- package/dist/types/crud.d.ts +22 -2
- package/dist/types/crud.d.ts.map +1 -1
- package/dist/types/plugins.d.ts +55 -1
- package/dist/types/plugins.d.ts.map +1 -1
- package/package.json +6 -18
- package/dist/blocks/index.d.ts +0 -25
- package/dist/blocks/index.d.ts.map +0 -1
- package/dist/chunk-5RJVUC6J.js.map +0 -1
- package/dist/chunk-L4TLMYGC.js.map +0 -1
- package/dist/chunk-V2MSAUHO.js.map +0 -1
- package/dist/chunk-X5THHG6T.js.map +0 -1
- package/dist/chunk-Y2C2FBQP.js +0 -35
- package/dist/chunk-Y2C2FBQP.js.map +0 -1
- package/dist/chunk-YH25Y4FW.js +0 -311
- package/dist/chunk-YH25Y4FW.js.map +0 -1
- package/dist/integrations/index.d.ts.map +0 -1
- package/dist/phone/index.d.ts.map +0 -1
- package/dist/seo/index.d.ts.map +0 -1
- package/dist/tenant/index.d.ts.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/src/access/denial.ts +0 -33
- package/src/access/engine.test.ts +0 -48
- package/src/access/engine.ts +0 -136
- package/src/access/index.ts +0 -14
- package/src/access/limits.ts +0 -18
- package/src/address/index.ts +0 -188
- package/src/app/render.tsx +0 -69
- package/src/blocks/index.tsx +0 -68
- package/src/data/archetype.ts +0 -255
- package/src/data/bulk.ts +0 -35
- package/src/data/cached.ts +0 -55
- package/src/data/count.test.ts +0 -152
- package/src/data/count.ts +0 -131
- package/src/data/filters.test.ts +0 -70
- package/src/data/filters.ts +0 -90
- package/src/data/index.ts +0 -11
- package/src/data/mock.ts +0 -90
- package/src/data/platform-api.ts +0 -209
- package/src/data/refresh.ts +0 -115
- package/src/data/resolve.ts +0 -126
- package/src/data/supabase.ts +0 -230
- package/src/data/types.ts +0 -50
- package/src/entity/index.ts +0 -2
- package/src/entity/registry.ts +0 -49
- package/src/events/index.tsx +0 -82
- package/src/i18n/index.ts +0 -476
- package/src/i18n/shell-translations.en.ts +0 -875
- package/src/i18n/shell-translations.pt-br.ts +0 -873
- package/src/index.ts +0 -217
- package/src/integrations/index.ts +0 -237
- package/src/lib/cache.ts +0 -118
- package/src/lib/csv.ts +0 -54
- package/src/lib/format.ts +0 -58
- package/src/manifest/app-manifest.schema.json +0 -676
- package/src/manifest/index.ts +0 -518
- package/src/manifest/serialize.ts +0 -52
- package/src/phone/index.ts +0 -85
- package/src/plugin/createSafeDataProvider.ts +0 -30
- package/src/plugin/index.ts +0 -1
- package/src/plugin/runtime.ts +0 -331
- package/src/registry/index.ts +0 -163
- package/src/router/history.ts +0 -86
- package/src/router/index.ts +0 -29
- package/src/runtime/index.ts +0 -15
- package/src/runtime/oauth.ts +0 -219
- package/src/search/engine.ts +0 -559
- package/src/search/index.ts +0 -25
- package/src/search/text.ts +0 -206
- package/src/search/types.ts +0 -75
- package/src/seo/index.ts +0 -128
- package/src/tenant/index.ts +0 -38
- package/src/testing/index.ts +0 -116
- package/src/types/auth.ts +0 -41
- package/src/types/billing.ts +0 -39
- package/src/types/crud.ts +0 -184
- package/src/types/entities.ts +0 -142
- package/src/types/entitlements.ts +0 -45
- package/src/types/i18n.ts +0 -7
- package/src/types/index.ts +0 -11
- package/src/types/org.ts +0 -137
- package/src/types/payment.ts +0 -44
- package/src/types/permissions.ts +0 -32
- package/src/types/plugins.ts +0 -480
- package/src/types/theme.ts +0 -8
package/src/manifest/index.ts
DELETED
|
@@ -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
|
-
}
|
package/src/phone/index.ts
DELETED
|
@@ -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
|
-
}
|
package/src/plugin/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { definePlugin, resolvePluginRuntime, getWidgetsForZone, PluginRuntimeProvider, usePluginRuntime, usePluginRuntimeOptional } from './runtime'
|