@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.
- package/README.md +2 -2
- package/dist/access/index.js.map +1 -1
- package/dist/chunk-BEGXYHS4.js +566 -0
- package/dist/chunk-BEGXYHS4.js.map +1 -0
- package/dist/chunk-ED6NBH6U.js +348 -0
- package/dist/chunk-ED6NBH6U.js.map +1 -0
- package/dist/chunk-EWKX4VUM.js +429 -0
- package/dist/chunk-EWKX4VUM.js.map +1 -0
- package/dist/chunk-LARZWDHA.js +319 -0
- package/dist/chunk-LARZWDHA.js.map +1 -0
- package/dist/chunk-OXQGRJZT.js +125 -0
- package/dist/chunk-OXQGRJZT.js.map +1 -0
- package/dist/chunk-RRYFXUQX.js +1247 -0
- package/dist/chunk-RRYFXUQX.js.map +1 -0
- package/dist/{chunk-5RJVUC6J.js → chunk-TDI6F6PT.js} +373 -25
- package/dist/chunk-TDI6F6PT.js.map +1 -0
- package/dist/{chunk-V2MSAUHO.js → chunk-UVKH6RDX.js} +211 -335
- package/dist/chunk-UVKH6RDX.js.map +1 -0
- package/dist/chunk-V53GR4UT.js +289 -0
- package/dist/chunk-V53GR4UT.js.map +1 -0
- package/dist/connector-runtime/index.d.ts +15 -0
- package/dist/connector-runtime/index.d.ts.map +1 -0
- package/dist/connector-runtime/index.js +3 -0
- package/dist/connector-runtime/index.js.map +1 -0
- package/dist/data/archetype.d.ts.map +1 -1
- package/dist/data/backend-env.d.ts +38 -0
- package/dist/data/backend-env.d.ts.map +1 -0
- package/dist/data/bulk.d.ts +24 -0
- package/dist/data/bulk.d.ts.map +1 -1
- package/dist/data/cached.d.ts.map +1 -1
- package/dist/data/custom-fields.d.ts +5 -0
- package/dist/data/custom-fields.d.ts.map +1 -0
- package/dist/data/index.d.ts +2 -1
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +3 -1
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/platform-api.d.ts.map +1 -1
- package/dist/data/resolve.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/types.d.ts +8 -0
- package/dist/data/types.d.ts.map +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/fields/store.d.ts +45 -0
- package/dist/fields/store.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/i18n/shell-translations.en.d.ts.map +1 -1
- package/dist/i18n/shell-translations.pt-br.d.ts.map +1 -1
- package/dist/index.d.ts +45 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +543 -56
- package/dist/index.js.map +1 -1
- package/dist/integrations/events.d.ts +7 -0
- package/dist/integrations/events.d.ts.map +1 -0
- package/dist/integrations/index.d.ts +533 -29
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js +3 -1
- package/dist/integrations/permissions.d.ts +132 -0
- package/dist/integrations/permissions.d.ts.map +1 -0
- package/dist/integrations/request.d.ts +170 -0
- package/dist/integrations/request.d.ts.map +1 -0
- package/dist/integrations/runtime-token.d.ts +33 -0
- package/dist/integrations/runtime-token.d.ts.map +1 -0
- package/dist/integrations/runtime.d.ts +398 -0
- package/dist/integrations/runtime.d.ts.map +1 -0
- package/dist/integrations/settings.d.ts +102 -0
- package/dist/integrations/settings.d.ts.map +1 -0
- package/dist/integrations/store.d.ts +145 -0
- package/dist/integrations/store.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 +3 -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 +179 -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/{shell-translations.pt-br-2GWRQRRP.js → shell-translations.pt-br-BM5ZFSKJ.js} +72 -3
- package/dist/shell-translations.pt-br-BM5ZFSKJ.js.map +1 -0
- package/dist/telemetry/errors.d.ts +87 -0
- package/dist/telemetry/errors.d.ts.map +1 -0
- package/dist/testing/index.js +4 -1
- package/dist/{testing/index.d.ts → testing.d.ts} +5 -3
- package/dist/testing.d.ts.map +1 -0
- package/dist/types/analytics.d.ts +213 -0
- package/dist/types/analytics.d.ts.map +1 -0
- package/dist/types/crud.d.ts +37 -3
- package/dist/types/crud.d.ts.map +1 -1
- package/dist/types/plugins.d.ts +138 -1
- package/dist/types/plugins.d.ts.map +1 -1
- package/package.json +9 -17
- 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 +0 -42
- package/dist/chunk-L4TLMYGC.js.map +0 -1
- package/dist/chunk-V2MSAUHO.js.map +0 -1
- package/dist/chunk-X5THHG6T.js +0 -77
- 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/phone/index.d.ts.map +0 -1
- package/dist/seo/index.d.ts.map +0 -1
- package/dist/shell-translations.pt-br-2GWRQRRP.js.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/README.md
CHANGED
|
@@ -40,7 +40,7 @@ export const crmPlugin = definePlugin({
|
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
## Part of the Fayz SDK
|
|
43
|
-
The headless core. `@fayz-ai/auth`, `@fayz-ai/ui`, and `@fayz-ai/
|
|
43
|
+
The headless core. `@fayz-ai/auth`, `@fayz-ai/ui`, and `@fayz-ai/admin` build the running app on top of it.
|
|
44
44
|
|
|
45
45
|
## Roadmap & contributing
|
|
46
|
-
Built and evolving in the open. See the [Fayz SDK roadmap](../../docs/ROADMAP.md#core) for current gaps, missing features, and good first issues.
|
|
46
|
+
Built and evolving in the open. See the [Fayz SDK roadmap](../../docs/platform/ROADMAP.md#core) for current gaps, missing features, and good first issues.
|
package/dist/access/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/access/engine.ts","../../src/access/denial.ts","../../src/access/limits.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { PermissionProfile, PermissionAction, SystemPermission } from '../types/permissions'\nimport type { Plan } from '../types/billing'\n\n// ---------------------------------------------------------------------------\n// The access engine — the ONE implementation of the role × plan × limit\n// decision, shared by every runtime that gates anything:\n//\n// - browser (saas AccessProvider / PermissionsProvider re-export from here)\n// - headless client-plane (saas access/headless.ts)\n// - the Fayz broker (server-side agent authorization imports this package)\n//\n// Hard constraint: this module (and everything under core/src/access) must stay\n// free of React, zustand and supabase — pure functions over plain data — so the\n// exact same code runs in Node/edge. Guarded by scripts/check-access-purity.mjs.\n// ---------------------------------------------------------------------------\n\n/** Why an access request was denied. `limit` only occurs on guarded writes. */\nexport type DenyReason = 'role' | 'plan' | 'limit'\n\nexport interface AccessDecision {\n allowed: boolean\n reason?: DenyReason\n}\n\n/** The minimal subject a decision needs — a projection of any richer session. */\nexport interface AccessSubject {\n /** Effective RBAC profile — the previewed role during impersonation. */\n profile: PermissionProfile | null\n /** The tenant's currently-active plan. */\n plan: Plan | null\n}\n\n/**\n * The org owner has every permission implicitly. Owner is represented by the\n * canonical role id `owner` across adapters (see org/adapters/supabase.ts\n * buildPermissionProfiles, which also grants owner all catalog perms, and the\n * mock adapter's `owner` fallback). We match by id first, falling back to the\n * (possibly localized) name for safety.\n */\nexport function isOwnerProfile(profile: PermissionProfile): boolean {\n return profile.id === 'owner' || profile.name?.toLowerCase() === 'owner'\n}\n\n/**\n * The single role-side permission check. The plan axis composes ON TOP of it in\n * {@link resolveAccess} instead of reimplementing owner-bypass / `manage`\n * semantics.\n */\nexport function profileHasPermission(\n profile: PermissionProfile | null,\n feature: string,\n action?: string,\n): boolean {\n if (!profile) return true\n\n // Owner bypass: never lock the owner out, even if the RBAC catalog is empty\n // or still loading. Because permission checks read the *effective* profile,\n // an owner previewing a non-owner role correctly loses this bypass — the\n // preview stays honest.\n if (isOwnerProfile(profile)) return true\n\n const actions = profile.grants[feature]\n if (!actions) return false\n\n if (!action) return actions.length > 0\n\n return actions.includes(action) || actions.includes('manage')\n}\n\n/**\n * The org-management side of a profile: managing team, billing, settings and\n * permissions themselves. Separate axis from `grants` — these gate the settings\n * tabs, not a feature's rows — and it follows the same owner bypass, so the one\n * person who can hand out access is never locked out of doing it.\n */\nexport function profileHasSystemPermission(\n profile: PermissionProfile | null,\n permission: SystemPermission,\n): boolean {\n if (!profile) return true\n if (isOwnerProfile(profile)) return true\n return profile.systemPermissions?.includes(permission) ?? false\n}\n\n/**\n * Does the current plan entitle `feature`? A plan gates a feature ONLY with an\n * explicit `false`; absent means \"not gated by this plan\" (allowed). This keeps\n * entitlements additive and safe: a feature the plan has never heard of stays\n * open rather than silently locking.\n */\nexport function isEntitledByPlan(plan: Plan | null, feature: string): boolean {\n return plan?.entitlements?.features?.[feature] !== false\n}\n\n/**\n * The single access decision: role FIRST, then plan.\n *\n * 1. Role — reuses `profileHasPermission` (owner bypass + `manage` semantics\n * live there, and impersonation composes for free because `profile` is the\n * previewed role). Denied → `{ allowed:false, reason:'role' }`.\n * 2. Plan — even the owner does NOT bypass the plan: upgrading is the tenant's\n * decision, not a role power. Denied → `{ allowed:false, reason:'plan' }`.\n */\nexport function resolveAccess(\n subject: AccessSubject,\n feature: string,\n action?: PermissionAction,\n): AccessDecision {\n if (!profileHasPermission(subject.profile, feature, action)) {\n return { allowed: false, reason: 'role' }\n }\n if (!isEntitledByPlan(subject.plan, feature)) {\n return { allowed: false, reason: 'plan' }\n }\n return { allowed: true }\n}\n\n/**\n * Pure numeric side of a limit: given the plan cap, whether a countable\n * declaration exists, and the current usage, derive the limit math. Unlimited\n * when there's no declaration to count, no cap on the plan, or the cap is `-1`.\n */\nexport function resolveLimit(params: { cap: number | undefined; hasDeclaration: boolean; used: number }): {\n max: number\n used: number\n remaining: number\n atLimit: boolean\n unlimited: boolean\n} {\n const { cap, hasDeclaration, used } = params\n const unlimited = !hasDeclaration || cap === undefined || cap === -1\n const max = unlimited ? Infinity : cap\n const remaining = unlimited ? Infinity : Math.max(0, max - used)\n const atLimit = !unlimited && used >= max\n return { max, used, remaining, atLimit, unlimited }\n}\n","import type { DenyReason } from './engine'\n\n// ---------------------------------------------------------------------------\n// The structured denial an AGENT surface returns instead of failing silently\n// (docs/ENTITLEMENTS.md § \"Superfície de agentes de IA\"). One shape for every\n// executor — FAB client-plane, the Fayz broker, and the pool RPCs (which emit\n// its jsonb mirror in their `denial` field) — so the model can explain the\n// denial and offer the upgrade path conversationally.\n// ---------------------------------------------------------------------------\n\n/** Where a denied tenant goes to upgrade — the conversational UpgradeModal. */\nexport const UPGRADE_URL = '/settings/subscription'\n\nexport interface AgentDenialLimit {\n key: string\n max: number\n used: number\n}\n\nexport interface AgentDenial {\n allowed: false\n reason: DenyReason\n /** Present when `reason === 'limit'`. */\n limit?: AgentDenialLimit\n upgradeUrl: string\n}\n\nexport type AgentGuardResult = { allowed: true } | AgentDenial\n\n/** Build a denial with the canonical upgrade URL attached. */\nexport function agentDenial(reason: DenyReason, limit?: AgentDenialLimit): AgentDenial {\n return { allowed: false, reason, ...(limit ? { limit } : {}), upgradeUrl: UPGRADE_URL }\n}\n","import type { LimitDeclaration } from '../types/entitlements'\n\n/**\n * The 4-layer limit-declaration merge, extracted so the browser AccessProvider\n * and the manifest derivation (`fayz manifest emit`) resolve the EXACT same\n * final set: core built-ins < entity-derived < plugin `declaredLimits` < app\n * overrides. Later layers win by key. Pure: pass the layers in, get the merge\n * out — deriving a declaration FROM an entity stays at the call site.\n */\nexport function mergeLimitDeclarations(\n ...layers: Array<LimitDeclaration[] | undefined>\n): LimitDeclaration[] {\n const byKey = new Map<string, LimitDeclaration>()\n for (const layer of layers) {\n for (const decl of layer ?? []) byKey.set(decl.key, decl)\n }\n return Array.from(byKey.values())\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/access/engine.ts","../../src/access/denial.ts","../../src/access/limits.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { PermissionProfile, PermissionAction, SystemPermission } from '../types/permissions'\nimport type { Plan } from '../types/billing'\n\n// ---------------------------------------------------------------------------\n// The access engine — the ONE implementation of the role × plan × limit\n// decision, shared by every runtime that gates anything:\n//\n// - browser (saas AccessProvider / PermissionsProvider re-export from here)\n// - headless client-plane (saas access/headless.ts)\n// - the Fayz broker (server-side agent authorization imports this package)\n//\n// Hard constraint: this module (and everything under core/src/access) must stay\n// free of React, zustand and supabase — pure functions over plain data — so the\n// exact same code runs in Node/edge. Guarded by scripts/check-access-purity.mjs.\n// ---------------------------------------------------------------------------\n\n/** Why an access request was denied. `limit` only occurs on guarded writes. */\nexport type DenyReason = 'role' | 'plan' | 'limit'\n\nexport interface AccessDecision {\n allowed: boolean\n reason?: DenyReason\n}\n\n/** The minimal subject a decision needs — a projection of any richer session. */\nexport interface AccessSubject {\n /** Effective RBAC profile — the previewed role during impersonation. */\n profile: PermissionProfile | null\n /** The tenant's currently-active plan. */\n plan: Plan | null\n}\n\n/**\n * The org owner has every permission implicitly. Owner is represented by the\n * canonical role id `owner` across adapters (see org/adapters/supabase.ts\n * buildPermissionProfiles, which also grants owner all catalog perms, and the\n * mock adapter's `owner` fallback). We match by id first, falling back to the\n * (possibly localized) name for safety.\n */\nexport function isOwnerProfile(profile: PermissionProfile): boolean {\n return profile.id === 'owner' || profile.name?.toLowerCase() === 'owner'\n}\n\n/**\n * The single role-side permission check. The plan axis composes ON TOP of it in\n * {@link resolveAccess} instead of reimplementing owner-bypass / `manage`\n * semantics.\n */\nexport function profileHasPermission(\n profile: PermissionProfile | null,\n feature: string,\n action?: string,\n): boolean {\n if (!profile) return true\n\n // Owner bypass: never lock the owner out, even if the RBAC catalog is empty\n // or still loading. Because permission checks read the *effective* profile,\n // an owner previewing a non-owner role correctly loses this bypass — the\n // preview stays honest.\n if (isOwnerProfile(profile)) return true\n\n const actions = profile.grants[feature]\n if (!actions) return false\n\n if (!action) return actions.length > 0\n\n return actions.includes(action) || actions.includes('manage')\n}\n\n/**\n * The org-management side of a profile: managing team, billing, settings and\n * permissions themselves. Separate axis from `grants` — these gate the settings\n * tabs, not a feature's rows — and it follows the same owner bypass, so the one\n * person who can hand out access is never locked out of doing it.\n */\nexport function profileHasSystemPermission(\n profile: PermissionProfile | null,\n permission: SystemPermission,\n): boolean {\n if (!profile) return true\n if (isOwnerProfile(profile)) return true\n return profile.systemPermissions?.includes(permission) ?? false\n}\n\n/**\n * Does the current plan entitle `feature`? A plan gates a feature ONLY with an\n * explicit `false`; absent means \"not gated by this plan\" (allowed). This keeps\n * entitlements additive and safe: a feature the plan has never heard of stays\n * open rather than silently locking.\n */\nexport function isEntitledByPlan(plan: Plan | null, feature: string): boolean {\n return plan?.entitlements?.features?.[feature] !== false\n}\n\n/**\n * The single access decision: role FIRST, then plan.\n *\n * 1. Role — reuses `profileHasPermission` (owner bypass + `manage` semantics\n * live there, and impersonation composes for free because `profile` is the\n * previewed role). Denied → `{ allowed:false, reason:'role' }`.\n * 2. Plan — even the owner does NOT bypass the plan: upgrading is the tenant's\n * decision, not a role power. Denied → `{ allowed:false, reason:'plan' }`.\n */\nexport function resolveAccess(\n subject: AccessSubject,\n feature: string,\n action?: PermissionAction,\n): AccessDecision {\n if (!profileHasPermission(subject.profile, feature, action)) {\n return { allowed: false, reason: 'role' }\n }\n if (!isEntitledByPlan(subject.plan, feature)) {\n return { allowed: false, reason: 'plan' }\n }\n return { allowed: true }\n}\n\n/**\n * Pure numeric side of a limit: given the plan cap, whether a countable\n * declaration exists, and the current usage, derive the limit math. Unlimited\n * when there's no declaration to count, no cap on the plan, or the cap is `-1`.\n */\nexport function resolveLimit(params: { cap: number | undefined; hasDeclaration: boolean; used: number }): {\n max: number\n used: number\n remaining: number\n atLimit: boolean\n unlimited: boolean\n} {\n const { cap, hasDeclaration, used } = params\n const unlimited = !hasDeclaration || cap === undefined || cap === -1\n const max = unlimited ? Infinity : cap\n const remaining = unlimited ? Infinity : Math.max(0, max - used)\n const atLimit = !unlimited && used >= max\n return { max, used, remaining, atLimit, unlimited }\n}\n","import type { DenyReason } from './engine'\n\n// ---------------------------------------------------------------------------\n// The structured denial an AGENT surface returns instead of failing silently\n// (docs/saas/ENTITLEMENTS.md § \"Superfície de agentes de IA\"). One shape for every\n// executor — FAB client-plane, the Fayz broker, and the pool RPCs (which emit\n// its jsonb mirror in their `denial` field) — so the model can explain the\n// denial and offer the upgrade path conversationally.\n// ---------------------------------------------------------------------------\n\n/** Where a denied tenant goes to upgrade — the conversational UpgradeModal. */\nexport const UPGRADE_URL = '/settings/subscription'\n\nexport interface AgentDenialLimit {\n key: string\n max: number\n used: number\n}\n\nexport interface AgentDenial {\n allowed: false\n reason: DenyReason\n /** Present when `reason === 'limit'`. */\n limit?: AgentDenialLimit\n upgradeUrl: string\n}\n\nexport type AgentGuardResult = { allowed: true } | AgentDenial\n\n/** Build a denial with the canonical upgrade URL attached. */\nexport function agentDenial(reason: DenyReason, limit?: AgentDenialLimit): AgentDenial {\n return { allowed: false, reason, ...(limit ? { limit } : {}), upgradeUrl: UPGRADE_URL }\n}\n","import type { LimitDeclaration } from '../types/entitlements'\n\n/**\n * The 4-layer limit-declaration merge, extracted so the browser AccessProvider\n * and the manifest derivation (`fayz manifest emit`) resolve the EXACT same\n * final set: core built-ins < entity-derived < plugin `declaredLimits` < app\n * overrides. Later layers win by key. Pure: pass the layers in, get the merge\n * out — deriving a declaration FROM an entity stays at the call site.\n */\nexport function mergeLimitDeclarations(\n ...layers: Array<LimitDeclaration[] | undefined>\n): LimitDeclaration[] {\n const byKey = new Map<string, LimitDeclaration>()\n for (const layer of layers) {\n for (const decl of layer ?? []) byKey.set(decl.key, decl)\n }\n return Array.from(byKey.values())\n}\n"]}
|
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import { getSupabaseClientOptional, getActiveTenantId } from './chunk-LARZWDHA.js';
|
|
2
|
+
|
|
3
|
+
// src/integrations/events.ts
|
|
4
|
+
function eventKeyMatches(pattern, key) {
|
|
5
|
+
if (pattern === "*") return true;
|
|
6
|
+
if (!pattern.endsWith("*")) return pattern === key;
|
|
7
|
+
const prefix = pattern.slice(0, -1);
|
|
8
|
+
return key.startsWith(prefix);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/integrations/permissions.ts
|
|
12
|
+
var DATA_ACTIONS = ["read", "write"];
|
|
13
|
+
var EVENT_ACTIONS = ["read", "listen", "emit"];
|
|
14
|
+
function permissionActions(kind) {
|
|
15
|
+
return kind === "data" ? DATA_ACTIONS : EVENT_ACTIONS;
|
|
16
|
+
}
|
|
17
|
+
function isDataPermission(permission) {
|
|
18
|
+
return permission.kind === "data";
|
|
19
|
+
}
|
|
20
|
+
function isEventPermission(permission) {
|
|
21
|
+
return permission.kind === "event";
|
|
22
|
+
}
|
|
23
|
+
function grants(permission, action) {
|
|
24
|
+
return permission.actions.includes(action);
|
|
25
|
+
}
|
|
26
|
+
function connectorPermissions(def) {
|
|
27
|
+
return def?.permissions ?? [];
|
|
28
|
+
}
|
|
29
|
+
function connectorEntities(def) {
|
|
30
|
+
const declared = connectorPermissions(def).filter(isDataPermission).map((p) => p.resource);
|
|
31
|
+
if (declared.length > 0) return unique(declared);
|
|
32
|
+
return unique(def?.entities ?? []);
|
|
33
|
+
}
|
|
34
|
+
function connectorSubscribesTo(def) {
|
|
35
|
+
const declared = connectorPermissions(def).filter((p) => isEventPermission(p) && grants(p, "listen")).map((p) => p.resource);
|
|
36
|
+
if (declared.length > 0) return unique(declared);
|
|
37
|
+
return unique(def?.subscribesTo ?? []);
|
|
38
|
+
}
|
|
39
|
+
function connectorSubscribes(def, key) {
|
|
40
|
+
return connectorSubscribesTo(def).some((pattern) => eventKeyMatches(pattern, key));
|
|
41
|
+
}
|
|
42
|
+
function permissionsWrite(def) {
|
|
43
|
+
return connectorPermissions(def).some((p) => grants(p, "write") || grants(p, "emit"));
|
|
44
|
+
}
|
|
45
|
+
function hasSensitivePermission(def) {
|
|
46
|
+
return connectorPermissions(def).some((p) => p.sensitive === true);
|
|
47
|
+
}
|
|
48
|
+
function sortedPermissions(def) {
|
|
49
|
+
return [...connectorPermissions(def)].sort((a, b) => {
|
|
50
|
+
if (a.kind !== b.kind) return a.kind === "data" ? -1 : 1;
|
|
51
|
+
const sensitivity = Number(b.sensitive ?? false) - Number(a.sensitive ?? false);
|
|
52
|
+
if (sensitivity !== 0) return sensitivity;
|
|
53
|
+
const mutating = Number(mutates(b)) - Number(mutates(a));
|
|
54
|
+
if (mutating !== 0) return mutating;
|
|
55
|
+
return a.resource.localeCompare(b.resource);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function mutates(permission) {
|
|
59
|
+
return grants(permission, "write") || grants(permission, "emit");
|
|
60
|
+
}
|
|
61
|
+
function groupedPermissions(def) {
|
|
62
|
+
const sorted = sortedPermissions(def);
|
|
63
|
+
return {
|
|
64
|
+
data: sorted.filter(isDataPermission),
|
|
65
|
+
events: sorted.filter(isEventPermission)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function connectorGrantScopes(def) {
|
|
69
|
+
const scopes = [];
|
|
70
|
+
for (const permission of connectorPermissions(def)) {
|
|
71
|
+
for (const action of permission.actions) scopes.push(`${permission.kind}:${permission.resource}:${action}`);
|
|
72
|
+
}
|
|
73
|
+
return unique(scopes).sort();
|
|
74
|
+
}
|
|
75
|
+
function unique(values) {
|
|
76
|
+
return [...new Set(values)];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/integrations/runtime-token.ts
|
|
80
|
+
var ConnectorRuntimeTokenUnavailableError = class extends Error {
|
|
81
|
+
constructor(message) {
|
|
82
|
+
super(message);
|
|
83
|
+
this.name = "ConnectorRuntimeTokenUnavailableError";
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var _source = null;
|
|
87
|
+
function setConnectorRuntimeTokenSource(source) {
|
|
88
|
+
_source = source;
|
|
89
|
+
}
|
|
90
|
+
function getConnectorRuntimeTokenSource() {
|
|
91
|
+
return _source;
|
|
92
|
+
}
|
|
93
|
+
async function connectorRuntimeToken(request) {
|
|
94
|
+
const source = _source;
|
|
95
|
+
if (!source) {
|
|
96
|
+
throw new ConnectorRuntimeTokenUnavailableError(
|
|
97
|
+
"This app installed no connector runtime-token source, so the data plane cannot prove which tenant it is for"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const value = typeof source === "function" ? await source(request) : source;
|
|
101
|
+
const token = typeof value === "string" ? value.trim() : "";
|
|
102
|
+
if (!token) {
|
|
103
|
+
throw new ConnectorRuntimeTokenUnavailableError(
|
|
104
|
+
"No runtime token is available for this session, so the call was not made"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return token;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/integrations/settings.ts
|
|
111
|
+
function isSettingSet(value) {
|
|
112
|
+
if (value === void 0 || value === null) return false;
|
|
113
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
114
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
function connectorSettings(def) {
|
|
118
|
+
return def.settings ?? [];
|
|
119
|
+
}
|
|
120
|
+
function missingRequiredSettings(settings, values) {
|
|
121
|
+
return (settings ?? []).filter((setting) => setting.requiredToOperate && !isSettingSet(values?.[setting.key])).map((setting) => setting.key);
|
|
122
|
+
}
|
|
123
|
+
function settingsStatus(settings, connection) {
|
|
124
|
+
if (!connection?.active) return { needsSetup: false };
|
|
125
|
+
const missing = missingRequiredSettings(settings, connection.settings);
|
|
126
|
+
return missing.length > 0 ? { needsSetup: true, missingSettings: missing } : { needsSetup: false };
|
|
127
|
+
}
|
|
128
|
+
function setupStatusDetail(missing) {
|
|
129
|
+
return `setup: ${missing.join(", ")}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/integrations/store.ts
|
|
133
|
+
var CONNECTIONS_TABLE = "plg_connections";
|
|
134
|
+
var SYNC_RUNS_TABLE = "plg_sync_runs";
|
|
135
|
+
var ConnectionSettingsError = class extends Error {
|
|
136
|
+
constructor(message) {
|
|
137
|
+
super(message);
|
|
138
|
+
this.name = "ConnectionSettingsError";
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
function assertSettingsAllowed(settings, options, connectorId = "connector") {
|
|
142
|
+
const secret = new Set((options.fields ?? []).filter((f) => f.type === "password").map((f) => f.key));
|
|
143
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
144
|
+
...(options.fields ?? []).filter((f) => f.type !== "password").map((f) => f.key),
|
|
145
|
+
// A declared setting IS its own allow-list entry. Requiring it in
|
|
146
|
+
// `settingsKeys` as well is two places to add a key and one place to forget.
|
|
147
|
+
...(options.settings ?? []).map((s) => s.key),
|
|
148
|
+
...options.settingsKeys ?? []
|
|
149
|
+
]);
|
|
150
|
+
for (const key of Object.keys(settings)) {
|
|
151
|
+
if (secret.has(key)) {
|
|
152
|
+
throw new ConnectionSettingsError(
|
|
153
|
+
`[${connectorId}] "${key}" is a credential field \u2014 credentials never reach plg_connections.settings.`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
if (!allowed.has(key)) {
|
|
157
|
+
throw new ConnectionSettingsError(
|
|
158
|
+
`[${connectorId}] unknown settings key "${key}". Declare it in the connector's fields or settingsKeys.`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function toConnection(row) {
|
|
164
|
+
return {
|
|
165
|
+
id: row.id,
|
|
166
|
+
tenantId: row.tenant_id,
|
|
167
|
+
connectorId: row.connector_id,
|
|
168
|
+
instanceKey: row.instance_key ?? "",
|
|
169
|
+
displayName: row.display_name ?? null,
|
|
170
|
+
active: row.active,
|
|
171
|
+
status: row.status,
|
|
172
|
+
statusDetail: row.status_detail ?? null,
|
|
173
|
+
settings: row.settings ?? {},
|
|
174
|
+
providerAccountKey: row.provider_account_key ?? null,
|
|
175
|
+
cursors: row.cursors ?? {},
|
|
176
|
+
lastSyncAt: row.last_sync_at ?? null,
|
|
177
|
+
createdAt: row.created_at,
|
|
178
|
+
updatedAt: row.updated_at
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function toRun(row) {
|
|
182
|
+
const stats = row.stats ?? {};
|
|
183
|
+
return {
|
|
184
|
+
id: row.id,
|
|
185
|
+
tenantId: row.tenant_id,
|
|
186
|
+
connectionId: row.connection_id,
|
|
187
|
+
connectorId: row.connector_id,
|
|
188
|
+
direction: row.direction,
|
|
189
|
+
triggerKind: row.trigger_kind,
|
|
190
|
+
status: row.status,
|
|
191
|
+
stream: row.stream ?? null,
|
|
192
|
+
fetched: row.fetched ?? 0,
|
|
193
|
+
written: row.written ?? 0,
|
|
194
|
+
stats,
|
|
195
|
+
cursor: row.cursor ?? null,
|
|
196
|
+
correlationId: row.correlation_id ?? null,
|
|
197
|
+
// `stats.message` is the pre-031 home; read it so a pool that has not
|
|
198
|
+
// applied 031 yet still shows the sentence rather than a blank row.
|
|
199
|
+
message: row.message ?? stats.message ?? null,
|
|
200
|
+
error: row.error ?? null,
|
|
201
|
+
startedAt: row.started_at,
|
|
202
|
+
finishedAt: row.finished_at ?? null
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function unwrap(answer, what) {
|
|
206
|
+
if (answer.error) throw new Error(`[@fayz-ai/core] ${what}: ${answer.error.message}`);
|
|
207
|
+
return answer.data;
|
|
208
|
+
}
|
|
209
|
+
function createConnectionStore(connectorId, options = {}) {
|
|
210
|
+
const instanceKey = options.instanceKey ?? "";
|
|
211
|
+
function client() {
|
|
212
|
+
const c = options.client ?? getSupabaseClientOptional();
|
|
213
|
+
if (!c) throw new Error("[@fayz-ai/core] Supabase client not available. Call createFayzApp with a provider first.");
|
|
214
|
+
return c;
|
|
215
|
+
}
|
|
216
|
+
function tenantId() {
|
|
217
|
+
const id = options.tenantId ?? getActiveTenantId();
|
|
218
|
+
if (!id) throw new Error(`[@fayz-ai/core] createConnectionStore(${connectorId}): no active tenant.`);
|
|
219
|
+
return id;
|
|
220
|
+
}
|
|
221
|
+
function scoped(q) {
|
|
222
|
+
return q.eq("tenant_id", tenantId()).eq("connector_id", connectorId).eq("instance_key", instanceKey);
|
|
223
|
+
}
|
|
224
|
+
const store = {
|
|
225
|
+
connectorId,
|
|
226
|
+
instanceKey,
|
|
227
|
+
async get() {
|
|
228
|
+
const row = unwrap(
|
|
229
|
+
await scoped(client().from(CONNECTIONS_TABLE).select("*")).maybeSingle(),
|
|
230
|
+
`read ${CONNECTIONS_TABLE}`
|
|
231
|
+
);
|
|
232
|
+
return row ? toConnection(row) : null;
|
|
233
|
+
},
|
|
234
|
+
async save(input = {}) {
|
|
235
|
+
const patch = input.settings ?? {};
|
|
236
|
+
assertSettingsAllowed(patch, options, connectorId);
|
|
237
|
+
const current = await store.get();
|
|
238
|
+
const settings = { ...current?.settings ?? {}, ...patch };
|
|
239
|
+
const row = {
|
|
240
|
+
tenant_id: tenantId(),
|
|
241
|
+
connector_id: connectorId,
|
|
242
|
+
instance_key: instanceKey,
|
|
243
|
+
settings,
|
|
244
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
245
|
+
};
|
|
246
|
+
if (input.displayName !== void 0) row.display_name = input.displayName;
|
|
247
|
+
if (input.active !== void 0) row.active = input.active;
|
|
248
|
+
if (input.providerAccountKey !== void 0) row.provider_account_key = input.providerAccountKey;
|
|
249
|
+
if (current?.id) row.id = current.id;
|
|
250
|
+
const missing = options.settings?.length ? missingRequiredSettings(options.settings, settings) : [];
|
|
251
|
+
const reactivating = input.active === true;
|
|
252
|
+
if (missing.length > 0) {
|
|
253
|
+
row.status = "pending";
|
|
254
|
+
row.status_detail = setupStatusDetail(missing);
|
|
255
|
+
} else if (!current || current.status === "pending" || reactivating && current.status === "revoked") {
|
|
256
|
+
row.status = "connected";
|
|
257
|
+
row.status_detail = null;
|
|
258
|
+
}
|
|
259
|
+
const saved = unwrap(
|
|
260
|
+
await client().from(CONNECTIONS_TABLE).upsert(row, { onConflict: "tenant_id,connector_id,instance_key" }).select("*").single(),
|
|
261
|
+
`write ${CONNECTIONS_TABLE}`
|
|
262
|
+
);
|
|
263
|
+
if (!saved) throw new Error(`[@fayz-ai/core] write ${CONNECTIONS_TABLE}: no row returned.`);
|
|
264
|
+
return toConnection(saved);
|
|
265
|
+
},
|
|
266
|
+
async disconnect() {
|
|
267
|
+
const answer = await scoped(
|
|
268
|
+
client().from(CONNECTIONS_TABLE).update({
|
|
269
|
+
active: false,
|
|
270
|
+
status: "revoked",
|
|
271
|
+
settings: {},
|
|
272
|
+
cursors: {},
|
|
273
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
unwrap(answer, `disconnect ${CONNECTIONS_TABLE}`);
|
|
277
|
+
},
|
|
278
|
+
async listRuns(opts = {}) {
|
|
279
|
+
const connection = await store.get();
|
|
280
|
+
if (!connection?.id) return [];
|
|
281
|
+
const answer = await client().from(SYNC_RUNS_TABLE).select("*").eq("connection_id", connection.id).order("started_at", { ascending: false }).limit(opts.limit ?? 20);
|
|
282
|
+
const rows = unwrap(answer, `read ${SYNC_RUNS_TABLE}`);
|
|
283
|
+
return (rows ?? []).map(toRun);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
return store;
|
|
287
|
+
}
|
|
288
|
+
function connectionKey(connectorId, instanceKey = "") {
|
|
289
|
+
return `${connectorId}\0${instanceKey}`;
|
|
290
|
+
}
|
|
291
|
+
async function listConnections(connectorIds, options = {}) {
|
|
292
|
+
const found = /* @__PURE__ */ new Map();
|
|
293
|
+
const ids = [...new Set(connectorIds)].filter(Boolean);
|
|
294
|
+
if (ids.length === 0) return found;
|
|
295
|
+
const c = options.client ?? getSupabaseClientOptional();
|
|
296
|
+
if (!c) throw new Error("[@fayz-ai/core] Supabase client not available. Call createFayzApp with a provider first.");
|
|
297
|
+
const tenantId = options.tenantId ?? getActiveTenantId();
|
|
298
|
+
if (!tenantId) throw new Error("[@fayz-ai/core] listConnections: no active tenant.");
|
|
299
|
+
const rows = unwrap(
|
|
300
|
+
await c.from(CONNECTIONS_TABLE).select("*").eq("tenant_id", tenantId).in("connector_id", ids),
|
|
301
|
+
`read ${CONNECTIONS_TABLE}`
|
|
302
|
+
);
|
|
303
|
+
for (const row of rows ?? []) {
|
|
304
|
+
const connection = toConnection(row);
|
|
305
|
+
found.set(connectionKey(connection.connectorId, connection.instanceKey), connection);
|
|
306
|
+
}
|
|
307
|
+
return found;
|
|
308
|
+
}
|
|
309
|
+
function connectionSettings(store) {
|
|
310
|
+
return {
|
|
311
|
+
async getSettings() {
|
|
312
|
+
return (await store.get())?.settings ?? {};
|
|
313
|
+
},
|
|
314
|
+
async saveSettings(values) {
|
|
315
|
+
await store.save({ settings: values });
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function connectionRuns(store) {
|
|
320
|
+
return {
|
|
321
|
+
async listRuns(options) {
|
|
322
|
+
try {
|
|
323
|
+
return await (typeof store === "function" ? store() : store).listRuns(options);
|
|
324
|
+
} catch {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function connectionStatus(connectorId, derive, options = {}) {
|
|
331
|
+
const instanceKey = options.instanceKey ?? "";
|
|
332
|
+
return {
|
|
333
|
+
instanceKey,
|
|
334
|
+
statusFromConnection: derive,
|
|
335
|
+
async getStatus() {
|
|
336
|
+
try {
|
|
337
|
+
const store = options.store?.() ?? createConnectionStore(connectorId, { instanceKey });
|
|
338
|
+
return derive(await store.get());
|
|
339
|
+
} catch {
|
|
340
|
+
return { connected: false };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/integrations/request.ts
|
|
347
|
+
var RequestBlockedError = class extends Error {
|
|
348
|
+
constructor(connectorId, reason, elapsedMs, detail) {
|
|
349
|
+
super(`[${connectorId}] request plane blocked: ${reason}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
350
|
+
this.name = "RequestBlockedError";
|
|
351
|
+
this.connectorId = connectorId;
|
|
352
|
+
this.reason = reason;
|
|
353
|
+
this.elapsedMs = elapsedMs;
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
var RequestPlaneError = class extends Error {
|
|
357
|
+
constructor(message) {
|
|
358
|
+
super(message);
|
|
359
|
+
this.name = "RequestPlaneError";
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
function requestPlaneSpec(def) {
|
|
363
|
+
const planes = def.planes ?? ["sync"];
|
|
364
|
+
if (!planes.includes("request")) {
|
|
365
|
+
throw new RequestPlaneError(`[${def.id}] is not on the request plane \u2014 declare planes: ['request'].`);
|
|
366
|
+
}
|
|
367
|
+
if (typeof def.latencyBudgetMs !== "number" || !Number.isFinite(def.latencyBudgetMs) || def.latencyBudgetMs <= 0) {
|
|
368
|
+
throw new RequestPlaneError(
|
|
369
|
+
`[${def.id}] request-plane connector must declare latencyBudgetMs \u2014 how long the buyer may wait.`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
if (def.onFailure !== "degrade" && def.onFailure !== "block") {
|
|
373
|
+
throw new RequestPlaneError(
|
|
374
|
+
`[${def.id}] request-plane connector must declare onFailure: 'degrade' | 'block'. Shipping degrades (the sale continues); payment blocks (the order does not confirm).`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
return { latencyBudgetMs: def.latencyBudgetMs, onFailure: def.onFailure };
|
|
378
|
+
}
|
|
379
|
+
function hasRequestPlane(def) {
|
|
380
|
+
try {
|
|
381
|
+
requestPlaneSpec(def);
|
|
382
|
+
return true;
|
|
383
|
+
} catch {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
async function runRequest(def, attempt) {
|
|
388
|
+
const spec = requestPlaneSpec(def);
|
|
389
|
+
const now = attempt.now ?? (() => Date.now());
|
|
390
|
+
const started = now();
|
|
391
|
+
const degradeOrBlock = async (reason, detail) => {
|
|
392
|
+
const elapsedMs = now() - started;
|
|
393
|
+
if (spec.onFailure === "block") throw new RequestBlockedError(def.id, reason, elapsedMs, detail);
|
|
394
|
+
if (!attempt.fallback) {
|
|
395
|
+
throw new RequestPlaneError(
|
|
396
|
+
`[${def.id}] declares onFailure: 'degrade' but the call supplied no fallback. Degrading requires an answer to degrade to.`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
value: await attempt.fallback(reason, detail),
|
|
401
|
+
source: "fallback",
|
|
402
|
+
elapsedMs,
|
|
403
|
+
degraded: { reason, detail }
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
if (attempt.skip) return degradeOrBlock("not-connected");
|
|
407
|
+
const controller = new AbortController();
|
|
408
|
+
let timer;
|
|
409
|
+
const budget = new Promise((resolve) => {
|
|
410
|
+
timer = setTimeout(() => resolve("timeout"), spec.latencyBudgetMs);
|
|
411
|
+
});
|
|
412
|
+
try {
|
|
413
|
+
const raced = await Promise.race([
|
|
414
|
+
attempt.call(controller.signal).then((value) => ({ ok: true, value })),
|
|
415
|
+
budget
|
|
416
|
+
]);
|
|
417
|
+
if (raced === "timeout") {
|
|
418
|
+
controller.abort();
|
|
419
|
+
return await degradeOrBlock("timeout", `budget of ${spec.latencyBudgetMs}ms spent`);
|
|
420
|
+
}
|
|
421
|
+
return { value: raced.value, source: "provider", elapsedMs: now() - started };
|
|
422
|
+
} catch (error) {
|
|
423
|
+
if (error instanceof RequestBlockedError || error instanceof RequestPlaneError) throw error;
|
|
424
|
+
return await degradeOrBlock("provider-error", error instanceof Error ? error.message : String(error));
|
|
425
|
+
} finally {
|
|
426
|
+
if (timer) clearTimeout(timer);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
async function runEffect(ledger, input, perform) {
|
|
430
|
+
if (!input.idempotencyKey) {
|
|
431
|
+
throw new RequestPlaneError("runEffect requires an idempotencyKey \u2014 an effect nobody can name twice is an effect that runs twice.");
|
|
432
|
+
}
|
|
433
|
+
const claim = await ledger.claim(input);
|
|
434
|
+
if (!claim.claimed) return { runId: claim.runId, performed: false };
|
|
435
|
+
try {
|
|
436
|
+
const value = await perform(claim.runId);
|
|
437
|
+
await ledger.settle({ runId: claim.runId, status: "success" });
|
|
438
|
+
return { runId: claim.runId, performed: true, value };
|
|
439
|
+
} catch (error) {
|
|
440
|
+
await ledger.settle({
|
|
441
|
+
runId: claim.runId,
|
|
442
|
+
status: "error",
|
|
443
|
+
error: error instanceof Error ? error.message : String(error)
|
|
444
|
+
});
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function createRpcEffectLedger(client) {
|
|
449
|
+
const call = async (fn, args) => {
|
|
450
|
+
const { data, error } = await client.rpc(fn, args);
|
|
451
|
+
if (error) throw new Error(`[@fayz-ai/core] ${fn}: ${error.message}`);
|
|
452
|
+
return data;
|
|
453
|
+
};
|
|
454
|
+
return {
|
|
455
|
+
async claim(input) {
|
|
456
|
+
const rows = await call("plg_claim_effect", {
|
|
457
|
+
p_connection_id: input.connectionId,
|
|
458
|
+
p_idempotency_key: input.idempotencyKey,
|
|
459
|
+
p_direction: input.direction,
|
|
460
|
+
p_trigger_kind: input.triggerKind,
|
|
461
|
+
p_stream: input.stream ?? null
|
|
462
|
+
});
|
|
463
|
+
const row = Array.isArray(rows) ? rows[0] : rows;
|
|
464
|
+
if (!row?.run_id) throw new Error("[@fayz-ai/core] plg_claim_effect returned no run");
|
|
465
|
+
return { runId: row.run_id, claimed: !!row.claimed };
|
|
466
|
+
},
|
|
467
|
+
async settle(input) {
|
|
468
|
+
await call("plg_settle_effect", {
|
|
469
|
+
p_run_id: input.runId,
|
|
470
|
+
p_status: input.status,
|
|
471
|
+
p_stats: input.stats ?? {},
|
|
472
|
+
p_error: input.error ?? null
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function buyerIdempotencyKey(parts) {
|
|
478
|
+
const { effect, subject, amountCents } = parts;
|
|
479
|
+
if (!effect || !subject) {
|
|
480
|
+
throw new RequestPlaneError("buyerIdempotencyKey needs both an effect and a subject (the order, the shipment).");
|
|
481
|
+
}
|
|
482
|
+
return amountCents === void 0 ? `${effect}:${subject}` : `${effect}:${subject}:${amountCents}`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/integrations/index.ts
|
|
486
|
+
var CONNECTOR_CATEGORIES = [
|
|
487
|
+
/** The provider IS a database or backend the app is built on. */
|
|
488
|
+
"database",
|
|
489
|
+
/** Money in: gateways, acquirers, Pix. */
|
|
490
|
+
"payments",
|
|
491
|
+
/** A bank account read (open banking, statements) — not a gateway. */
|
|
492
|
+
"banking",
|
|
493
|
+
/** Stock, invoicing, back office. */
|
|
494
|
+
"erp",
|
|
495
|
+
/** Freight quotes, labels, tracking. */
|
|
496
|
+
"logistics",
|
|
497
|
+
/** Someone else's storefront the business also sells on (iFood, Shopee). */
|
|
498
|
+
"marketplace",
|
|
499
|
+
/** The business's OWN online store. */
|
|
500
|
+
"ecommerce",
|
|
501
|
+
/** Contacts, deals, pipeline. */
|
|
502
|
+
"crm",
|
|
503
|
+
/** Courses sold by a third party (Hotmart, Kiwify). */
|
|
504
|
+
"courses",
|
|
505
|
+
/** Bookings and calendars. */
|
|
506
|
+
"scheduling",
|
|
507
|
+
/** Conversations: WhatsApp, SMS, email campaigns. */
|
|
508
|
+
"messaging",
|
|
509
|
+
/** Measurement, pixels, SEO. */
|
|
510
|
+
"analytics",
|
|
511
|
+
/** Presence on a map or a directory (Google Meu Negócio). */
|
|
512
|
+
"local",
|
|
513
|
+
/** Models and agent frameworks. */
|
|
514
|
+
"ai",
|
|
515
|
+
/** Wiring between other tools (Zapier, n8n). */
|
|
516
|
+
"automation"
|
|
517
|
+
];
|
|
518
|
+
var CATEGORY_SET = new Set(CONNECTOR_CATEGORIES);
|
|
519
|
+
function isConnectorCategory(value) {
|
|
520
|
+
return typeof value === "string" && CATEGORY_SET.has(value);
|
|
521
|
+
}
|
|
522
|
+
function invocationPlane(moment) {
|
|
523
|
+
return moment.plane ?? "request";
|
|
524
|
+
}
|
|
525
|
+
function connectorPlanes(def) {
|
|
526
|
+
return def.planes?.length ? def.planes : ["sync"];
|
|
527
|
+
}
|
|
528
|
+
function preferredPlane(def) {
|
|
529
|
+
const planes = connectorPlanes(def);
|
|
530
|
+
return planes.includes("agentic") ? "agentic" : planes[0];
|
|
531
|
+
}
|
|
532
|
+
function connectorDirection(def) {
|
|
533
|
+
return def.direction ?? "outbound";
|
|
534
|
+
}
|
|
535
|
+
function withAfterHooks(target, methods) {
|
|
536
|
+
return new Proxy(target, {
|
|
537
|
+
get(obj, prop, receiver) {
|
|
538
|
+
const orig = Reflect.get(obj, prop, receiver);
|
|
539
|
+
const hook = methods[prop];
|
|
540
|
+
if (typeof orig !== "function" || !hook) return orig;
|
|
541
|
+
return (...args) => {
|
|
542
|
+
const result = orig.apply(obj, args);
|
|
543
|
+
if (result instanceof Promise) {
|
|
544
|
+
return result.then((value) => {
|
|
545
|
+
try {
|
|
546
|
+
void Promise.resolve(hook(args, value)).catch(() => {
|
|
547
|
+
});
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
return value;
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
try {
|
|
554
|
+
void Promise.resolve(hook(args, result)).catch(() => {
|
|
555
|
+
});
|
|
556
|
+
} catch {
|
|
557
|
+
}
|
|
558
|
+
return result;
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export { CONNECTIONS_TABLE, CONNECTOR_CATEGORIES, ConnectionSettingsError, ConnectorRuntimeTokenUnavailableError, RequestBlockedError, RequestPlaneError, SYNC_RUNS_TABLE, assertSettingsAllowed, buyerIdempotencyKey, connectionKey, connectionRuns, connectionSettings, connectionStatus, connectorDirection, connectorEntities, connectorGrantScopes, connectorPermissions, connectorPlanes, connectorRuntimeToken, connectorSettings, connectorSubscribes, connectorSubscribesTo, createConnectionStore, createRpcEffectLedger, eventKeyMatches, getConnectorRuntimeTokenSource, grants, groupedPermissions, hasRequestPlane, hasSensitivePermission, invocationPlane, isConnectorCategory, isDataPermission, isEventPermission, isSettingSet, listConnections, missingRequiredSettings, permissionActions, permissionsWrite, preferredPlane, requestPlaneSpec, runEffect, runRequest, setConnectorRuntimeTokenSource, settingsStatus, setupStatusDetail, sortedPermissions, withAfterHooks };
|
|
565
|
+
//# sourceMappingURL=chunk-BEGXYHS4.js.map
|
|
566
|
+
//# sourceMappingURL=chunk-BEGXYHS4.js.map
|