@fayz-ai/core 0.8.4 → 0.8.5
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/dist/access/engine.d.ts +8 -1
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/index.d.ts +1 -1
- package/dist/access/index.d.ts.map +1 -1
- package/dist/access/index.js +6 -1
- package/dist/access/index.js.map +1 -1
- package/dist/chunk-HN7PTOSH.js +1149 -0
- package/dist/chunk-HN7PTOSH.js.map +1 -0
- package/dist/{chunk-KL2XT2TV.cjs → chunk-V2MSAUHO.js} +166 -29
- package/dist/chunk-V2MSAUHO.js.map +1 -0
- package/dist/data/archetype.d.ts.map +1 -1
- package/dist/data/bulk.d.ts +17 -0
- package/dist/data/bulk.d.ts.map +1 -0
- package/dist/data/filters.d.ts +29 -0
- package/dist/data/filters.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 +1 -1
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/platform-api.d.ts.map +1 -1
- package/dist/data/refresh.d.ts.map +1 -1
- package/dist/data/resolve.d.ts +10 -0
- package/dist/data/resolve.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/types.d.ts +22 -0
- package/dist/data/types.d.ts.map +1 -1
- package/dist/i18n/index.d.ts +7 -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 +2 -0
- package/dist/i18n/shell-translations.en.d.ts.map +1 -0
- package/dist/i18n/shell-translations.pt-br.d.ts +2 -0
- package/dist/i18n/shell-translations.pt-br.d.ts.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -6
- package/dist/index.js.map +1 -1
- package/dist/search/engine.d.ts.map +1 -1
- package/dist/shell-translations.pt-br-2GWRQRRP.js +875 -0
- package/dist/shell-translations.pt-br-2GWRQRRP.js.map +1 -0
- package/dist/tenant/index.d.ts +10 -0
- package/dist/tenant/index.d.ts.map +1 -1
- package/dist/types/org.d.ts +9 -3
- package/dist/types/org.d.ts.map +1 -1
- package/package.json +21 -29
- package/src/access/engine.ts +16 -1
- package/src/access/index.ts +1 -0
- package/src/app/render.tsx +1 -1
- package/src/data/archetype.ts +23 -0
- package/src/data/bulk.ts +35 -0
- package/src/data/filters.test.ts +70 -0
- package/src/data/filters.ts +90 -0
- package/src/data/index.ts +2 -1
- package/src/data/mock.ts +8 -2
- package/src/data/platform-api.ts +12 -3
- package/src/data/refresh.ts +11 -1
- package/src/data/resolve.ts +16 -4
- package/src/data/supabase.ts +60 -1
- package/src/data/types.ts +24 -0
- package/src/i18n/index.ts +56 -6
- package/src/i18n/shell-translations.en.ts +875 -0
- package/src/i18n/shell-translations.pt-br.ts +873 -0
- package/src/index.ts +11 -2
- package/src/search/engine.ts +8 -0
- package/src/tenant/index.ts +20 -0
- package/src/types/org.ts +9 -3
- package/dist/access/index.cjs +0 -60
- package/dist/access/index.cjs.map +0 -1
- package/dist/chunk-56GH3WC2.cjs +0 -35
- package/dist/chunk-56GH3WC2.cjs.map +0 -1
- package/dist/chunk-5TKIZVVJ.cjs +0 -16
- package/dist/chunk-5TKIZVVJ.cjs.map +0 -1
- package/dist/chunk-AXTWKF5X.cjs +0 -38
- package/dist/chunk-AXTWKF5X.cjs.map +0 -1
- package/dist/chunk-GULZD7XW.js +0 -709
- package/dist/chunk-GULZD7XW.js.map +0 -1
- package/dist/chunk-HN66DF4E.cjs +0 -2024
- package/dist/chunk-HN66DF4E.cjs.map +0 -1
- package/dist/chunk-KL2XT2TV.cjs.map +0 -1
- package/dist/chunk-MJ4ML56F.js +0 -1994
- package/dist/chunk-MJ4ML56F.js.map +0 -1
- package/dist/chunk-P5OJUH25.cjs +0 -81
- package/dist/chunk-P5OJUH25.cjs.map +0 -1
- package/dist/chunk-UKU4RIWM.cjs +0 -32
- package/dist/chunk-UKU4RIWM.cjs.map +0 -1
- package/dist/chunk-ZOPJB5FN.cjs +0 -369
- package/dist/chunk-ZOPJB5FN.cjs.map +0 -1
- package/dist/data/index.cjs +0 -40
- package/dist/data/index.cjs.map +0 -1
- package/dist/entity/index.cjs +0 -28
- package/dist/entity/index.cjs.map +0 -1
- package/dist/i18n/index.cjs +0 -44
- package/dist/i18n/index.cjs.map +0 -1
- package/dist/i18n/shell-translations.d.ts +0 -2
- package/dist/i18n/shell-translations.d.ts.map +0 -1
- package/dist/index.cjs +0 -2450
- package/dist/index.cjs.map +0 -1
- package/dist/integrations/index.cjs +0 -12
- package/dist/integrations/index.cjs.map +0 -1
- package/dist/plugin/index.cjs +0 -32
- package/dist/plugin/index.cjs.map +0 -1
- package/dist/router/index.cjs +0 -16
- package/dist/router/index.cjs.map +0 -1
- package/dist/runtime/index.cjs +0 -16
- package/dist/runtime/index.cjs.map +0 -1
- package/dist/testing/index.cjs +0 -20
- package/dist/testing/index.cjs.map +0 -1
- package/dist/types/index.cjs +0 -4
- package/dist/types/index.cjs.map +0 -1
- package/src/i18n/shell-translations.ts +0 -1752
package/src/index.ts
CHANGED
|
@@ -20,6 +20,15 @@ export { createMockProvider } from './data/mock'
|
|
|
20
20
|
export { createArchetypeProvider } from './data/archetype'
|
|
21
21
|
export { withCache } from './data/cached'
|
|
22
22
|
export { resolveDataProvider } from './data/resolve'
|
|
23
|
+
export { bulkCreate } from './data/bulk'
|
|
24
|
+
export type { BulkWriteOptions, BulkWriteResult } from './data/types'
|
|
25
|
+
export {
|
|
26
|
+
parseRangeFilter,
|
|
27
|
+
isFilterObject,
|
|
28
|
+
matchesRange,
|
|
29
|
+
applyRangeToQuery,
|
|
30
|
+
} from './data/filters'
|
|
31
|
+
export type { RangeFilter } from './data/filters'
|
|
23
32
|
export {
|
|
24
33
|
DATA_CHANGED_EVENT,
|
|
25
34
|
emitDataChanged,
|
|
@@ -48,7 +57,7 @@ export type { SeoConfig, BreadcrumbItem } from './seo'
|
|
|
48
57
|
export type { DataProvider, CrudQuery, CrudResult, SupabaseProviderConfig, FayzApiProviderConfig } from './data/index'
|
|
49
58
|
|
|
50
59
|
// Tenant context (runtime DI for the data layer)
|
|
51
|
-
export { setActiveTenantId, getActiveTenantId } from './tenant'
|
|
60
|
+
export { setActiveTenantId, getActiveTenantId, subscribeActiveTenant, useActiveTenantId } from './tenant'
|
|
52
61
|
|
|
53
62
|
// Runtime broker helpers
|
|
54
63
|
export { createFayzRuntimeClient, FayzRuntimeError } from './runtime'
|
|
@@ -188,7 +197,7 @@ export { defineApp, renderApp, useManifest } from './app/render'
|
|
|
188
197
|
export type { RenderAppOptions } from './app/render'
|
|
189
198
|
|
|
190
199
|
// i18n
|
|
191
|
-
export { I18nProvider, useI18nConfig, useTranslation, coreTranslations, mergeTranslations, registerTranslations, getCurrentLocale, setCurrentLocale, setLocaleStore } from './i18n/index'
|
|
200
|
+
export { I18nProvider, useI18nConfig, useTranslation, coreTranslations, mergeTranslations, registerTranslations, getCurrentLocale, setCurrentLocale, preloadLocale, setLocaleStore } from './i18n/index'
|
|
192
201
|
|
|
193
202
|
// Router
|
|
194
203
|
export { hashRouterAdapter, windowRouterAdapter } from './router/index'
|
package/src/search/engine.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { EntityDef } from '../types/crud'
|
|
2
2
|
import { resolveDataProvider } from '../data/resolve'
|
|
3
3
|
import { getSupabaseClientOptional } from '../data/supabase'
|
|
4
|
+
import { DATA_CHANGED_EVENT } from '../data/refresh'
|
|
5
|
+
import { eventBus } from '../events'
|
|
4
6
|
import { getActiveTenantId } from '../tenant'
|
|
5
7
|
import {
|
|
6
8
|
digitsOf,
|
|
@@ -286,6 +288,12 @@ export function clearSearchCache(): void {
|
|
|
286
288
|
cache.length = 0
|
|
287
289
|
}
|
|
288
290
|
|
|
291
|
+
// The engine owns this cache, so the engine subscribes — asking every writer to
|
|
292
|
+
// remember `clearSearchCache()` is how it ended up called from nowhere at all,
|
|
293
|
+
// leaving global search (and the assistant's findAnything, which rides on it)
|
|
294
|
+
// answering "não encontrei" about a record written seconds earlier.
|
|
295
|
+
eventBus.on(DATA_CHANGED_EVENT, clearSearchCache)
|
|
296
|
+
|
|
289
297
|
// Path ① — the server index
|
|
290
298
|
|
|
291
299
|
interface IndexRow {
|
package/src/tenant/index.ts
CHANGED
|
@@ -2,17 +2,37 @@
|
|
|
2
2
|
// scoping and cache keying. The host (saas shell / app) sets it from its org
|
|
3
3
|
// store via setActiveTenantId; the data providers stay decoupled from any
|
|
4
4
|
// specific org-store implementation (runtime DI).
|
|
5
|
+
import * as React from 'react'
|
|
5
6
|
import { clearGlobalCache } from '../lib/cache'
|
|
6
7
|
|
|
7
8
|
let _activeTenantId: string | undefined
|
|
9
|
+
const listeners = new Set<() => void>()
|
|
8
10
|
|
|
9
11
|
export function setActiveTenantId(id: string | undefined): void {
|
|
10
12
|
if (id !== _activeTenantId) {
|
|
11
13
|
_activeTenantId = id
|
|
12
14
|
clearGlobalCache()
|
|
15
|
+
for (const l of listeners) l()
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export function getActiveTenantId(): string | undefined {
|
|
17
20
|
return _activeTenantId
|
|
18
21
|
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Subscribes to tenant changes. A getter alone makes every read a race with the
|
|
25
|
+
* shell: a view that mounts before the org store resolves reads `undefined`,
|
|
26
|
+
* and a tenant-scoped fetch that answers "no tenant" looks exactly like "no
|
|
27
|
+
* rows" — it never retries and the screen stays empty until a remount.
|
|
28
|
+
*/
|
|
29
|
+
export function subscribeActiveTenant(listener: () => void): () => void {
|
|
30
|
+
listeners.add(listener)
|
|
31
|
+
return () => { listeners.delete(listener) }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The active tenant, re-rendering when it resolves or changes. Use this to key
|
|
35
|
+
* effects that fetch tenant-scoped data. */
|
|
36
|
+
export function useActiveTenantId(): string | undefined {
|
|
37
|
+
return React.useSyncExternalStore(subscribeActiveTenant, getActiveTenantId, getActiveTenantId)
|
|
38
|
+
}
|
package/src/types/org.ts
CHANGED
|
@@ -40,11 +40,16 @@ export interface OrgMembership {
|
|
|
40
40
|
* A team member in the Person-first model: a person (of a configured team kind)
|
|
41
41
|
* with an OPTIONAL access overlay. `membership` is present only when the person
|
|
42
42
|
* has login + RBAC role (a `tenant_members` row linked by `person_id`).
|
|
43
|
+
*
|
|
44
|
+
* The inverse also exists: a membership with no person behind it (the owner who
|
|
45
|
+
* created the workspace). Those rows carry `membership` and no `personId`/`kind`.
|
|
43
46
|
*/
|
|
44
47
|
export interface TeamPerson {
|
|
45
|
-
|
|
48
|
+
/** Absent on a login-only row: a member with no person record behind it. */
|
|
49
|
+
personId?: string
|
|
46
50
|
name: string
|
|
47
|
-
|
|
51
|
+
/** Absent on a login-only row. */
|
|
52
|
+
kind?: string
|
|
48
53
|
email?: string
|
|
49
54
|
avatarUrl?: string
|
|
50
55
|
isActive: boolean
|
|
@@ -102,7 +107,8 @@ export interface OrgAdapter {
|
|
|
102
107
|
listMembers(orgId: string): Promise<OrgMember[]>
|
|
103
108
|
/**
|
|
104
109
|
* Person-first team list: people whose `kind` is in `personKinds`, each with an
|
|
105
|
-
* optional membership overlay (login + role) linked by `tenant_members.person_id
|
|
110
|
+
* optional membership overlay (login + role) linked by `tenant_members.person_id`,
|
|
111
|
+
* PLUS the memberships that match no such person (they'd otherwise be invisible).
|
|
106
112
|
* Optional so adapters can adopt it incrementally; the Team screen falls back to
|
|
107
113
|
* `listMembers` when absent or when no `personKinds` are configured.
|
|
108
114
|
*/
|
package/dist/access/index.cjs
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// src/access/engine.ts
|
|
4
|
-
function isOwnerProfile(profile) {
|
|
5
|
-
return profile.id === "owner" || profile.name?.toLowerCase() === "owner";
|
|
6
|
-
}
|
|
7
|
-
function profileHasPermission(profile, feature, action) {
|
|
8
|
-
if (!profile) return true;
|
|
9
|
-
if (isOwnerProfile(profile)) return true;
|
|
10
|
-
const actions = profile.grants[feature];
|
|
11
|
-
if (!actions) return false;
|
|
12
|
-
if (!action) return actions.length > 0;
|
|
13
|
-
return actions.includes(action) || actions.includes("manage");
|
|
14
|
-
}
|
|
15
|
-
function isEntitledByPlan(plan, feature) {
|
|
16
|
-
return plan?.entitlements?.features?.[feature] !== false;
|
|
17
|
-
}
|
|
18
|
-
function resolveAccess(subject, feature, action) {
|
|
19
|
-
if (!profileHasPermission(subject.profile, feature, action)) {
|
|
20
|
-
return { allowed: false, reason: "role" };
|
|
21
|
-
}
|
|
22
|
-
if (!isEntitledByPlan(subject.plan, feature)) {
|
|
23
|
-
return { allowed: false, reason: "plan" };
|
|
24
|
-
}
|
|
25
|
-
return { allowed: true };
|
|
26
|
-
}
|
|
27
|
-
function resolveLimit(params) {
|
|
28
|
-
const { cap, hasDeclaration, used } = params;
|
|
29
|
-
const unlimited = !hasDeclaration || cap === void 0 || cap === -1;
|
|
30
|
-
const max = unlimited ? Infinity : cap;
|
|
31
|
-
const remaining = unlimited ? Infinity : Math.max(0, max - used);
|
|
32
|
-
const atLimit = !unlimited && used >= max;
|
|
33
|
-
return { max, used, remaining, atLimit, unlimited };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// src/access/denial.ts
|
|
37
|
-
var UPGRADE_URL = "/settings/subscription";
|
|
38
|
-
function agentDenial(reason, limit) {
|
|
39
|
-
return { allowed: false, reason, ...limit ? { limit } : {}, upgradeUrl: UPGRADE_URL };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// src/access/limits.ts
|
|
43
|
-
function mergeLimitDeclarations(...layers) {
|
|
44
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
45
|
-
for (const layer of layers) {
|
|
46
|
-
for (const decl of layer ?? []) byKey.set(decl.key, decl);
|
|
47
|
-
}
|
|
48
|
-
return Array.from(byKey.values());
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
exports.UPGRADE_URL = UPGRADE_URL;
|
|
52
|
-
exports.agentDenial = agentDenial;
|
|
53
|
-
exports.isEntitledByPlan = isEntitledByPlan;
|
|
54
|
-
exports.isOwnerProfile = isOwnerProfile;
|
|
55
|
-
exports.mergeLimitDeclarations = mergeLimitDeclarations;
|
|
56
|
-
exports.profileHasPermission = profileHasPermission;
|
|
57
|
-
exports.resolveAccess = resolveAccess;
|
|
58
|
-
exports.resolveLimit = resolveLimit;
|
|
59
|
-
//# sourceMappingURL=index.cjs.map
|
|
60
|
-
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/access/engine.ts","../../src/access/denial.ts","../../src/access/limits.ts"],"names":[],"mappings":";;;AAuCO,SAAS,eAAe,OAAA,EAAqC;AAClE,EAAA,OAAO,QAAQ,EAAA,KAAO,OAAA,IAAW,OAAA,CAAQ,IAAA,EAAM,aAAY,KAAM,OAAA;AACnE;AAOO,SAAS,oBAAA,CACd,OAAA,EACA,OAAA,EACA,MAAA,EACS;AACT,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAMrB,EAAA,IAAI,cAAA,CAAe,OAAO,CAAA,EAAG,OAAO,IAAA;AAEpC,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,MAAA,CAAO,OAAO,CAAA;AACtC,EAAA,IAAI,CAAC,SAAS,OAAO,KAAA;AAErB,EAAA,IAAI,CAAC,MAAA,EAAQ,OAAO,OAAA,CAAQ,MAAA,GAAS,CAAA;AAErC,EAAA,OAAO,QAAQ,QAAA,CAAS,MAAM,CAAA,IAAK,OAAA,CAAQ,SAAS,QAAQ,CAAA;AAC9D;AAQO,SAAS,gBAAA,CAAiB,MAAmB,OAAA,EAA0B;AAC5E,EAAA,OAAO,IAAA,EAAM,YAAA,EAAc,QAAA,GAAW,OAAO,CAAA,KAAM,KAAA;AACrD;AAWO,SAAS,aAAA,CACd,OAAA,EACA,OAAA,EACA,MAAA,EACgB;AAChB,EAAA,IAAI,CAAC,oBAAA,CAAqB,OAAA,CAAQ,OAAA,EAAS,OAAA,EAAS,MAAM,CAAA,EAAG;AAC3D,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,MAAA,EAAQ,MAAA,EAAO;AAAA,EAC1C;AACA,EAAA,IAAI,CAAC,gBAAA,CAAiB,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA,EAAG;AAC5C,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,MAAA,EAAQ,MAAA,EAAO;AAAA,EAC1C;AACA,EAAA,OAAO,EAAE,SAAS,IAAA,EAAK;AACzB;AAOO,SAAS,aAAa,MAAA,EAM3B;AACA,EAAA,MAAM,EAAE,GAAA,EAAK,cAAA,EAAgB,IAAA,EAAK,GAAI,MAAA;AACtC,EAAA,MAAM,SAAA,GAAY,CAAC,cAAA,IAAkB,GAAA,KAAQ,UAAa,GAAA,KAAQ,EAAA;AAClE,EAAA,MAAM,GAAA,GAAM,YAAY,QAAA,GAAW,GAAA;AACnC,EAAA,MAAM,YAAY,SAAA,GAAY,QAAA,GAAW,KAAK,GAAA,CAAI,CAAA,EAAG,MAAM,IAAI,CAAA;AAC/D,EAAA,MAAM,OAAA,GAAU,CAAC,SAAA,IAAa,IAAA,IAAQ,GAAA;AACtC,EAAA,OAAO,EAAE,GAAA,EAAK,IAAA,EAAM,SAAA,EAAW,SAAS,SAAA,EAAU;AACpD;;;AC7GO,IAAM,WAAA,GAAc;AAmBpB,SAAS,WAAA,CAAY,QAAoB,KAAA,EAAuC;AACrF,EAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,MAAA,EAAQ,GAAI,KAAA,GAAQ,EAAE,KAAA,EAAM,GAAI,EAAC,EAAI,UAAA,EAAY,WAAA,EAAY;AACxF;;;ACvBO,SAAS,0BACX,MAAA,EACiB;AACpB,EAAA,MAAM,KAAA,uBAAY,GAAA,EAA8B;AAChD,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,KAAA,MAAW,IAAA,IAAQ,SAAS,EAAC,QAAS,GAAA,CAAI,IAAA,CAAK,KAAK,IAAI,CAAA;AAAA,EAC1D;AACA,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,CAAA;AAClC","file":"index.cjs","sourcesContent":["import type { PermissionProfile, PermissionAction } 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 * 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"]}
|
package/dist/chunk-56GH3WC2.cjs
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// src/integrations/index.ts
|
|
4
|
-
function withAfterHooks(target, methods) {
|
|
5
|
-
return new Proxy(target, {
|
|
6
|
-
get(obj, prop, receiver) {
|
|
7
|
-
const orig = Reflect.get(obj, prop, receiver);
|
|
8
|
-
const hook = methods[prop];
|
|
9
|
-
if (typeof orig !== "function" || !hook) return orig;
|
|
10
|
-
return (...args) => {
|
|
11
|
-
const result = orig.apply(obj, args);
|
|
12
|
-
if (result instanceof Promise) {
|
|
13
|
-
return result.then((value) => {
|
|
14
|
-
try {
|
|
15
|
-
void Promise.resolve(hook(args, value)).catch(() => {
|
|
16
|
-
});
|
|
17
|
-
} catch {
|
|
18
|
-
}
|
|
19
|
-
return value;
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
void Promise.resolve(hook(args, result)).catch(() => {
|
|
24
|
-
});
|
|
25
|
-
} catch {
|
|
26
|
-
}
|
|
27
|
-
return result;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
exports.withAfterHooks = withAfterHooks;
|
|
34
|
-
//# sourceMappingURL=chunk-56GH3WC2.cjs.map
|
|
35
|
-
//# sourceMappingURL=chunk-56GH3WC2.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/integrations/index.ts"],"names":[],"mappings":";;;AAmJO,SAAS,cAAA,CACd,QACA,OAAA,EACG;AACH,EAAA,OAAO,IAAI,MAAM,MAAA,EAAQ;AAAA,IACvB,GAAA,CAAI,GAAA,EAAK,IAAA,EAAM,QAAA,EAAU;AACvB,MAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,GAAA,CAAI,GAAA,EAAK,MAAM,QAAQ,CAAA;AAC5C,MAAA,MAAM,IAAA,GAAQ,QAA8G,IAAI,CAAA;AAChI,MAAA,IAAI,OAAO,IAAA,KAAS,UAAA,IAAc,CAAC,MAAM,OAAO,IAAA;AAChD,MAAA,OAAO,IAAI,IAAA,KAAoB;AAC7B,QAAA,MAAM,MAAA,GAAU,IAAA,CAAsC,KAAA,CAAM,GAAA,EAAK,IAAI,CAAA;AACrE,QAAA,IAAI,kBAAkB,OAAA,EAAS;AAC7B,UAAA,OAAO,MAAA,CAAO,IAAA,CAAK,CAAC,KAAA,KAAU;AAC5B,YAAA,IAAI;AAAE,cAAA,KAAK,OAAA,CAAQ,QAAQ,IAAA,CAAK,IAAA,EAAM,KAAK,CAAC,CAAA,CAAE,MAAM,MAAM;AAAA,cAAC,CAAC,CAAA;AAAA,YAAE,CAAA,CAAA,MAAQ;AAAA,YAA6B;AACnG,YAAA,OAAO,KAAA;AAAA,UACT,CAAC,CAAA;AAAA,QACH;AACA,QAAA,IAAI;AAAE,UAAA,KAAK,OAAA,CAAQ,QAAQ,IAAA,CAAK,IAAA,EAAM,MAAM,CAAC,CAAA,CAAE,MAAM,MAAM;AAAA,UAAC,CAAC,CAAA;AAAA,QAAE,CAAA,CAAA,MAAQ;AAAA,QAAa;AACpF,QAAA,OAAO,MAAA;AAAA,MACT,CAAA;AAAA,IACF;AAAA,GACD,CAAA;AACH","file":"chunk-56GH3WC2.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// Integration Connector spine\n// ---------------------------------------------------------------------------\n// An integration is a connector that keeps canonical Fayz data in agreement\n// with an external provider (Google Calendar, open banking, Shopify, …). This\n// module is the thin shared contract both native (SDK) and client-built\n// connectors implement. The heavy lifting (auth, fetch/push, idempotent\n// upserts) runs in the data plane — typically a Supabase Edge Function — while\n// the control plane (a settings tab + this contract) lives in the app/plugin.\n//\n// Design notes live in docs/design/bling-integration-brief.md. Proven precedents:\n// the Fayz runtime OAuth broker (packages/core/src/runtime/oauth.ts) and the\n// bank-statement edge functions in the predecessor app.\nimport type * as React from 'react'\n\n/** How a connector authenticates to its provider. */\nexport type IntegrationAuthKind = 'oauth' | 'api-key' | 'mtls'\n\n/** Which way data flows for a given capability. */\nexport type SyncDirection = 'inbound' | 'outbound' | 'bidirectional'\n\n/** What causes a sync to run. */\nexport type SyncTrigger = 'on-write' | 'scheduled' | 'manual' | 'webhook'\n\n/** One thing a connector can sync (an entity + its direction + triggers). */\nexport interface ConnectorCapability {\n /** Canonical entity synced, e.g. 'booking', 'movement'. */\n entity: string\n direction: SyncDirection\n triggers: SyncTrigger[]\n}\n\n/** The state of a tenant's connection to a provider (one row in a *_integrations table). */\nexport interface ConnectionConfig {\n /** Provider key, e.g. 'google', 'plugbank'. */\n provider: string\n /** Whether the connection is active and should sync. */\n active: boolean\n /** Opaque, provider-specific config (calendar id, account id, cursor, …). Never secrets. */\n settings?: Record<string, unknown>\n /** Last successful sync, ISO timestamp. */\n lastSyncAt?: string\n}\n\n/** Audit record of one sync execution (generalizes *_sync_log / *_sync_runs). */\nexport interface SyncRun {\n id?: string\n provider: string\n direction: SyncDirection\n trigger: SyncTrigger\n status: 'success' | 'partial' | 'error'\n /** Items pulled/pushed and how many were written. */\n fetched?: number\n written?: number\n /** Pagination/incremental cursor advanced by this run. */\n cursor?: string\n error?: string\n startedAt?: string\n finishedAt?: string\n}\n\nexport interface TestConnectionResult {\n ok: boolean\n message?: string\n}\n\n/**\n * A connector's control-plane descriptor. The data-plane verbs (testConnection,\n * sync) are usually thin clients that invoke a Supabase Edge Function — the\n * function holds credentials and does the real work.\n */\nexport interface Connector {\n /** Stable connector id, e.g. 'google-calendar', 'plugbank'. */\n id: string\n /** Provider key matching ConnectionConfig.provider. */\n provider: string\n /** Plugin this connector extends, e.g. 'agenda', 'financial'. */\n pluginId: string\n authKind: IntegrationAuthKind\n capabilities: ConnectorCapability[]\n /** Validate the stored credentials/config without syncing. */\n testConnection?(config: ConnectionConfig): Promise<TestConnectionResult>\n /** Run a sync for the given direction; returns the audit record. */\n sync?(input: { config: ConnectionConfig; direction: SyncDirection; trigger: SyncTrigger }): Promise<SyncRun>\n}\n\n// ---------------------------------------------------------------------------\n// Connector UI contract — how an addon plugin EXTENDS a host plugin's settings\n// ---------------------------------------------------------------------------\n// An addon plugin declares one or more ConnectorDefinitions on its manifest\n// (`connectors: [...]`). The runtime groups them by `hostPluginId`, and the host\n// plugin's settings panel renders them in a unified \"Integrations\" tab — the\n// same connect/credentials experience for every connector. The connector-\n// specific bits (import a statement, sync now, history) live in `ExtraPanel`.\n\n/** A credential input the unified setup form renders for an api-key/mtls connector. */\nexport interface ConnectorField {\n key: string\n label: string\n type: 'text' | 'password'\n placeholder?: string\n}\n\nexport interface ConnectorStatus {\n connected: boolean\n detail?: string\n}\n\n/**\n * UI-facing connector descriptor. Built on the data-plane `Connector` concept\n * (`pluginId` → `hostPluginId`), plus the control-plane hooks the unified\n * Integrations hub calls. Lives in core so `PluginManifest.connectors` can type\n * it; the rendering hub lives in `@fayz-ai/saas`.\n */\nexport interface ConnectorDefinition {\n /** Stable connector id, e.g. 'google-calendar', 'plugbank'. */\n id: string\n /** The plugin this connector extends, e.g. 'financial', 'agenda'. */\n hostPluginId: string\n name: string\n description?: string\n /** Lucide icon name. */\n icon?: string\n authKind: IntegrationAuthKind\n /** Declarative credentials — the unified form renders these (api-key / mtls). */\n fields?: ConnectorField[]\n /** Current connection state for the status badge. */\n getStatus(): Promise<ConnectorStatus>\n /** Validate credentials without persisting (api-key / mtls). */\n testConnection?(values: Record<string, string>): Promise<TestConnectionResult>\n /** Persist the connection (api-key / mtls). */\n saveConnection?(values: Record<string, string>): Promise<void>\n /** Begin an OAuth connect flow — returns the consent URL to redirect to. */\n startOAuth?(redirectTo?: string): Promise<string>\n /** Tear down the connection. */\n disconnect?(): Promise<void>\n /** Optional connector-specific UI below the connect panel (import / sync / history). */\n ExtraPanel?: React.ComponentType\n}\n\n/**\n * Wrap an object's async methods so a handler fires AFTER selected methods\n * resolve — the non-invasive hook for outbound, event-driven sync when there's\n * no app-level event bus. Example: wrap an AgendaDataProvider so that after\n * createBooking/updateBooking/deleteBooking succeed, an external calendar is\n * updated. The handler runs fire-and-forget; its failures never break the call.\n */\nexport function withAfterHooks<T extends object>(\n target: T,\n methods: Partial<Record<keyof T, (args: unknown[], result: unknown) => void | Promise<void>>>,\n): T {\n return new Proxy(target, {\n get(obj, prop, receiver) {\n const orig = Reflect.get(obj, prop, receiver)\n const hook = (methods as Record<string | symbol, ((args: unknown[], result: unknown) => void | Promise<void>) | undefined>)[prop]\n if (typeof orig !== 'function' || !hook) return orig\n return (...args: unknown[]) => {\n const result = (orig as (...a: unknown[]) => unknown).apply(obj, args)\n if (result instanceof Promise) {\n return result.then((value) => {\n try { void Promise.resolve(hook(args, value)).catch(() => {}) } catch { /* never break the call */ }\n return value\n })\n }\n try { void Promise.resolve(hook(args, result)).catch(() => {}) } catch { /* noop */ }\n return result\n }\n },\n })\n}\n"]}
|
package/dist/chunk-5TKIZVVJ.cjs
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var runtime = require('@fayz-ai/sdk/runtime');
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Object.defineProperty(exports, "FayzRuntimeError", {
|
|
8
|
-
enumerable: true,
|
|
9
|
-
get: function () { return runtime.FayzRuntimeError; }
|
|
10
|
-
});
|
|
11
|
-
Object.defineProperty(exports, "createFayzRuntimeClient", {
|
|
12
|
-
enumerable: true,
|
|
13
|
-
get: function () { return runtime.createFayzRuntimeClient; }
|
|
14
|
-
});
|
|
15
|
-
//# sourceMappingURL=chunk-5TKIZVVJ.cjs.map
|
|
16
|
-
//# sourceMappingURL=chunk-5TKIZVVJ.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-5TKIZVVJ.cjs","sourcesContent":[]}
|
package/dist/chunk-AXTWKF5X.cjs
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// src/router/index.ts
|
|
4
|
-
function hashRouterAdapter() {
|
|
5
|
-
return {
|
|
6
|
-
navigate(to) {
|
|
7
|
-
window.location.hash = to;
|
|
8
|
-
},
|
|
9
|
-
getCurrentPath() {
|
|
10
|
-
return window.location.hash.slice(1) || "/";
|
|
11
|
-
},
|
|
12
|
-
onPathChange(cb) {
|
|
13
|
-
const handler = () => cb(window.location.hash.slice(1) || "/");
|
|
14
|
-
window.addEventListener("hashchange", handler);
|
|
15
|
-
return () => window.removeEventListener("hashchange", handler);
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
function windowRouterAdapter() {
|
|
20
|
-
return {
|
|
21
|
-
navigate(to) {
|
|
22
|
-
window.history.pushState(null, "", to);
|
|
23
|
-
},
|
|
24
|
-
getCurrentPath() {
|
|
25
|
-
return window.location.pathname;
|
|
26
|
-
},
|
|
27
|
-
onPathChange(cb) {
|
|
28
|
-
const handler = () => cb(window.location.pathname);
|
|
29
|
-
window.addEventListener("popstate", handler);
|
|
30
|
-
return () => window.removeEventListener("popstate", handler);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
exports.hashRouterAdapter = hashRouterAdapter;
|
|
36
|
-
exports.windowRouterAdapter = windowRouterAdapter;
|
|
37
|
-
//# sourceMappingURL=chunk-AXTWKF5X.cjs.map
|
|
38
|
-
//# sourceMappingURL=chunk-AXTWKF5X.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/router/index.ts"],"names":[],"mappings":";;;AAMO,SAAS,iBAAA,GAAmC;AACjD,EAAA,OAAO;AAAA,IACL,SAAS,EAAA,EAAI;AAAE,MAAA,MAAA,CAAO,SAAS,IAAA,GAAO,EAAA;AAAA,IAAG,CAAA;AAAA,IACzC,cAAA,GAAiB;AAAE,MAAA,OAAO,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA,IAAK,GAAA;AAAA,IAAI,CAAA;AAAA,IAC/D,aAAa,EAAA,EAAI;AACf,MAAA,MAAM,OAAA,GAAU,MAAM,EAAA,CAAG,MAAA,CAAO,SAAS,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA,IAAK,GAAG,CAAA;AAC7D,MAAA,MAAA,CAAO,gBAAA,CAAiB,cAAc,OAAO,CAAA;AAC7C,MAAA,OAAO,MAAM,MAAA,CAAO,mBAAA,CAAoB,YAAA,EAAc,OAAO,CAAA;AAAA,IAC/D;AAAA,GACF;AACF;AAEO,SAAS,mBAAA,GAAqC;AACnD,EAAA,OAAO;AAAA,IACL,SAAS,EAAA,EAAI;AAAE,MAAA,MAAA,CAAO,OAAA,CAAQ,SAAA,CAAU,IAAA,EAAM,EAAA,EAAI,EAAE,CAAA;AAAA,IAAE,CAAA;AAAA,IACtD,cAAA,GAAiB;AAAE,MAAA,OAAO,OAAO,QAAA,CAAS,QAAA;AAAA,IAAS,CAAA;AAAA,IACnD,aAAa,EAAA,EAAI;AACf,MAAA,MAAM,OAAA,GAAU,MAAM,EAAA,CAAG,MAAA,CAAO,SAAS,QAAQ,CAAA;AACjD,MAAA,MAAA,CAAO,gBAAA,CAAiB,YAAY,OAAO,CAAA;AAC3C,MAAA,OAAO,MAAM,MAAA,CAAO,mBAAA,CAAoB,UAAA,EAAY,OAAO,CAAA;AAAA,IAC7D;AAAA,GACF;AACF","file":"chunk-AXTWKF5X.cjs","sourcesContent":["export interface RouterAdapter {\n navigate(to: string): void\n getCurrentPath(): string\n onPathChange(cb: (path: string) => void): () => void\n}\n\nexport function hashRouterAdapter(): RouterAdapter {\n return {\n navigate(to) { window.location.hash = to },\n getCurrentPath() { return window.location.hash.slice(1) || '/' },\n onPathChange(cb) {\n const handler = () => cb(window.location.hash.slice(1) || '/')\n window.addEventListener('hashchange', handler)\n return () => window.removeEventListener('hashchange', handler)\n },\n }\n}\n\nexport function windowRouterAdapter(): RouterAdapter {\n return {\n navigate(to) { window.history.pushState(null, '', to) },\n getCurrentPath() { return window.location.pathname },\n onPathChange(cb) {\n const handler = () => cb(window.location.pathname)\n window.addEventListener('popstate', handler)\n return () => window.removeEventListener('popstate', handler)\n },\n }\n}\n"]}
|