@fayz-ai/core 0.8.1 → 0.8.2
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/denial.d.ts +21 -0
- package/dist/access/denial.d.ts.map +1 -0
- package/dist/access/engine.d.ts +63 -0
- package/dist/access/engine.d.ts.map +1 -0
- package/dist/access/index.cjs +60 -0
- package/dist/access/index.cjs.map +1 -0
- package/dist/access/index.d.ts +6 -0
- package/dist/access/index.d.ts.map +1 -0
- package/dist/access/index.js +51 -0
- package/dist/access/index.js.map +1 -0
- package/dist/access/limits.d.ts +10 -0
- package/dist/access/limits.d.ts.map +1 -0
- package/dist/address/index.d.ts +52 -0
- package/dist/address/index.d.ts.map +1 -0
- package/dist/{chunk-WKZVSKEU.js → chunk-CU64QI4A.js} +2 -2
- package/dist/chunk-CU64QI4A.js.map +1 -0
- package/dist/{chunk-54SRL7AJ.cjs → chunk-CYAWDW44.cjs} +141 -84
- package/dist/chunk-CYAWDW44.cjs.map +1 -0
- package/dist/{chunk-KNIVT6XZ.js → chunk-JHGXROKV.js} +230 -2
- package/dist/chunk-JHGXROKV.js.map +1 -0
- package/dist/{chunk-QR7U2URY.cjs → chunk-SO3VUKGZ.cjs} +230 -2
- package/dist/chunk-SO3VUKGZ.cjs.map +1 -0
- package/dist/{chunk-2NTHNWEH.cjs → chunk-UKU4RIWM.cjs} +2 -2
- package/dist/chunk-UKU4RIWM.cjs.map +1 -0
- package/dist/{chunk-ZBIWYDAR.js → chunk-XJKW2JRW.js} +140 -85
- package/dist/chunk-XJKW2JRW.js.map +1 -0
- package/dist/{chunk-CHGX6UYR.js → chunk-YH25Y4FW.js} +6 -3
- package/dist/chunk-YH25Y4FW.js.map +1 -0
- package/dist/{chunk-5X2VX3RQ.cjs → chunk-ZOPJB5FN.cjs} +6 -3
- package/dist/chunk-ZOPJB5FN.cjs.map +1 -0
- package/dist/data/count.d.ts +34 -0
- package/dist/data/count.d.ts.map +1 -0
- package/dist/data/index.cjs +18 -10
- package/dist/data/index.d.ts +2 -0
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +1 -1
- package/dist/data/resolve.d.ts.map +1 -1
- package/dist/entity/index.cjs +6 -6
- package/dist/entity/index.js +1 -1
- package/dist/entity/registry.d.ts +9 -0
- package/dist/entity/registry.d.ts.map +1 -1
- package/dist/i18n/index.cjs +10 -10
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/shell-translations.d.ts.map +1 -1
- package/dist/index.cjs +852 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +765 -56
- package/dist/index.js.map +1 -1
- package/dist/manifest/index.d.ts +109 -3
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/manifest/serialize.d.ts +14 -0
- package/dist/manifest/serialize.d.ts.map +1 -0
- package/dist/plugin/index.cjs +7 -7
- package/dist/plugin/index.js +1 -1
- package/dist/plugin/runtime.d.ts.map +1 -1
- package/dist/types/billing.d.ts +20 -0
- package/dist/types/billing.d.ts.map +1 -1
- package/dist/types/crud.d.ts +47 -1
- package/dist/types/crud.d.ts.map +1 -1
- package/dist/types/entitlements.d.ts +45 -0
- package/dist/types/entitlements.d.ts.map +1 -0
- package/dist/types/index.d.ts +4 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/plugins.d.ts +84 -0
- package/dist/types/plugins.d.ts.map +1 -1
- package/package.json +14 -4
- package/src/access/denial.ts +33 -0
- package/src/access/engine.test.ts +48 -0
- package/src/access/engine.ts +121 -0
- package/src/access/index.ts +13 -0
- package/src/access/limits.ts +18 -0
- package/src/address/index.ts +188 -0
- package/src/data/count.test.ts +152 -0
- package/src/data/count.ts +131 -0
- package/src/data/index.ts +2 -0
- package/src/data/resolve.ts +7 -1
- package/src/entity/registry.ts +7 -0
- package/src/i18n/shell-translations.ts +230 -0
- package/src/index.ts +18 -3
- package/src/manifest/app-manifest.schema.json +618 -45
- package/src/manifest/index.ts +127 -3
- package/src/manifest/serialize.ts +52 -0
- package/src/plugin/runtime.ts +5 -1
- package/src/types/billing.ts +22 -0
- package/src/types/crud.ts +48 -1
- package/src/types/entitlements.ts +45 -0
- package/src/types/index.ts +4 -3
- package/src/types/plugins.ts +79 -0
- package/dist/chunk-2NTHNWEH.cjs.map +0 -1
- package/dist/chunk-54SRL7AJ.cjs.map +0 -1
- package/dist/chunk-5X2VX3RQ.cjs.map +0 -1
- package/dist/chunk-CHGX6UYR.js.map +0 -1
- package/dist/chunk-KNIVT6XZ.js.map +0 -1
- package/dist/chunk-QR7U2URY.cjs.map +0 -1
- package/dist/chunk-WKZVSKEU.js.map +0 -1
- package/dist/chunk-ZBIWYDAR.js.map +0 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DenyReason } from './engine';
|
|
2
|
+
/** Where a denied tenant goes to upgrade — the conversational UpgradeModal. */
|
|
3
|
+
export declare const UPGRADE_URL = "/settings/subscription";
|
|
4
|
+
export interface AgentDenialLimit {
|
|
5
|
+
key: string;
|
|
6
|
+
max: number;
|
|
7
|
+
used: number;
|
|
8
|
+
}
|
|
9
|
+
export interface AgentDenial {
|
|
10
|
+
allowed: false;
|
|
11
|
+
reason: DenyReason;
|
|
12
|
+
/** Present when `reason === 'limit'`. */
|
|
13
|
+
limit?: AgentDenialLimit;
|
|
14
|
+
upgradeUrl: string;
|
|
15
|
+
}
|
|
16
|
+
export type AgentGuardResult = {
|
|
17
|
+
allowed: true;
|
|
18
|
+
} | AgentDenial;
|
|
19
|
+
/** Build a denial with the canonical upgrade URL attached. */
|
|
20
|
+
export declare function agentDenial(reason: DenyReason, limit?: AgentDenialLimit): AgentDenial;
|
|
21
|
+
//# sourceMappingURL=denial.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"denial.d.ts","sourceRoot":"","sources":["../../src/access/denial.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAU1C,+EAA+E;AAC/E,eAAO,MAAM,WAAW,2BAA2B,CAAA;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,KAAK,CAAA;IACd,MAAM,EAAE,UAAU,CAAA;IAClB,yCAAyC;IACzC,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,gBAAgB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG,WAAW,CAAA;AAE9D,8DAA8D;AAC9D,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,gBAAgB,GAAG,WAAW,CAErF"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { PermissionProfile, PermissionAction } from '../types/permissions';
|
|
2
|
+
import type { Plan } from '../types/billing';
|
|
3
|
+
/** Why an access request was denied. `limit` only occurs on guarded writes. */
|
|
4
|
+
export type DenyReason = 'role' | 'plan' | 'limit';
|
|
5
|
+
export interface AccessDecision {
|
|
6
|
+
allowed: boolean;
|
|
7
|
+
reason?: DenyReason;
|
|
8
|
+
}
|
|
9
|
+
/** The minimal subject a decision needs — a projection of any richer session. */
|
|
10
|
+
export interface AccessSubject {
|
|
11
|
+
/** Effective RBAC profile — the previewed role during impersonation. */
|
|
12
|
+
profile: PermissionProfile | null;
|
|
13
|
+
/** The tenant's currently-active plan. */
|
|
14
|
+
plan: Plan | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The org owner has every permission implicitly. Owner is represented by the
|
|
18
|
+
* canonical role id `owner` across adapters (see org/adapters/supabase.ts
|
|
19
|
+
* buildPermissionProfiles, which also grants owner all catalog perms, and the
|
|
20
|
+
* mock adapter's `owner` fallback). We match by id first, falling back to the
|
|
21
|
+
* (possibly localized) name for safety.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isOwnerProfile(profile: PermissionProfile): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The single role-side permission check. The plan axis composes ON TOP of it in
|
|
26
|
+
* {@link resolveAccess} instead of reimplementing owner-bypass / `manage`
|
|
27
|
+
* semantics.
|
|
28
|
+
*/
|
|
29
|
+
export declare function profileHasPermission(profile: PermissionProfile | null, feature: string, action?: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Does the current plan entitle `feature`? A plan gates a feature ONLY with an
|
|
32
|
+
* explicit `false`; absent means "not gated by this plan" (allowed). This keeps
|
|
33
|
+
* entitlements additive and safe: a feature the plan has never heard of stays
|
|
34
|
+
* open rather than silently locking.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isEntitledByPlan(plan: Plan | null, feature: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* The single access decision: role FIRST, then plan.
|
|
39
|
+
*
|
|
40
|
+
* 1. Role — reuses `profileHasPermission` (owner bypass + `manage` semantics
|
|
41
|
+
* live there, and impersonation composes for free because `profile` is the
|
|
42
|
+
* previewed role). Denied → `{ allowed:false, reason:'role' }`.
|
|
43
|
+
* 2. Plan — even the owner does NOT bypass the plan: upgrading is the tenant's
|
|
44
|
+
* decision, not a role power. Denied → `{ allowed:false, reason:'plan' }`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveAccess(subject: AccessSubject, feature: string, action?: PermissionAction): AccessDecision;
|
|
47
|
+
/**
|
|
48
|
+
* Pure numeric side of a limit: given the plan cap, whether a countable
|
|
49
|
+
* declaration exists, and the current usage, derive the limit math. Unlimited
|
|
50
|
+
* when there's no declaration to count, no cap on the plan, or the cap is `-1`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveLimit(params: {
|
|
53
|
+
cap: number | undefined;
|
|
54
|
+
hasDeclaration: boolean;
|
|
55
|
+
used: number;
|
|
56
|
+
}): {
|
|
57
|
+
max: number;
|
|
58
|
+
used: number;
|
|
59
|
+
remaining: number;
|
|
60
|
+
atLimit: boolean;
|
|
61
|
+
unlimited: boolean;
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/access/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAe5C,+EAA+E;AAC/E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAElD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,UAAU,CAAA;CACpB;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACjC,0CAA0C;IAC1C,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAElE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,GAAG,IAAI,EACjC,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAeT;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAE5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,gBAAgB,GACxB,cAAc,CAQhB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IACxG,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,OAAO,CAAA;CACnB,CAOA"}
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { isOwnerProfile, profileHasPermission, isEntitledByPlan, resolveAccess, resolveLimit, } from './engine';
|
|
2
|
+
export type { DenyReason, AccessDecision, AccessSubject } from './engine';
|
|
3
|
+
export { UPGRADE_URL, agentDenial } from './denial';
|
|
4
|
+
export type { AgentDenial, AgentDenialLimit, AgentGuardResult } from './denial';
|
|
5
|
+
export { mergeLimitDeclarations } from './limits';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/access/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,YAAY,GACb,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAEzE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AACnD,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAE/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/access/engine.ts
|
|
2
|
+
function isOwnerProfile(profile) {
|
|
3
|
+
return profile.id === "owner" || profile.name?.toLowerCase() === "owner";
|
|
4
|
+
}
|
|
5
|
+
function profileHasPermission(profile, feature, action) {
|
|
6
|
+
if (!profile) return true;
|
|
7
|
+
if (isOwnerProfile(profile)) return true;
|
|
8
|
+
const actions = profile.grants[feature];
|
|
9
|
+
if (!actions) return false;
|
|
10
|
+
if (!action) return actions.length > 0;
|
|
11
|
+
return actions.includes(action) || actions.includes("manage");
|
|
12
|
+
}
|
|
13
|
+
function isEntitledByPlan(plan, feature) {
|
|
14
|
+
return plan?.entitlements?.features?.[feature] !== false;
|
|
15
|
+
}
|
|
16
|
+
function resolveAccess(subject, feature, action) {
|
|
17
|
+
if (!profileHasPermission(subject.profile, feature, action)) {
|
|
18
|
+
return { allowed: false, reason: "role" };
|
|
19
|
+
}
|
|
20
|
+
if (!isEntitledByPlan(subject.plan, feature)) {
|
|
21
|
+
return { allowed: false, reason: "plan" };
|
|
22
|
+
}
|
|
23
|
+
return { allowed: true };
|
|
24
|
+
}
|
|
25
|
+
function resolveLimit(params) {
|
|
26
|
+
const { cap, hasDeclaration, used } = params;
|
|
27
|
+
const unlimited = !hasDeclaration || cap === void 0 || cap === -1;
|
|
28
|
+
const max = unlimited ? Infinity : cap;
|
|
29
|
+
const remaining = unlimited ? Infinity : Math.max(0, max - used);
|
|
30
|
+
const atLimit = !unlimited && used >= max;
|
|
31
|
+
return { max, used, remaining, atLimit, unlimited };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/access/denial.ts
|
|
35
|
+
var UPGRADE_URL = "/settings/subscription";
|
|
36
|
+
function agentDenial(reason, limit) {
|
|
37
|
+
return { allowed: false, reason, ...limit ? { limit } : {}, upgradeUrl: UPGRADE_URL };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/access/limits.ts
|
|
41
|
+
function mergeLimitDeclarations(...layers) {
|
|
42
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
43
|
+
for (const layer of layers) {
|
|
44
|
+
for (const decl of layer ?? []) byKey.set(decl.key, decl);
|
|
45
|
+
}
|
|
46
|
+
return Array.from(byKey.values());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { UPGRADE_URL, agentDenial, isEntitledByPlan, isOwnerProfile, mergeLimitDeclarations, profileHasPermission, resolveAccess, resolveLimit };
|
|
50
|
+
//# sourceMappingURL=index.js.map
|
|
51
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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.js","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"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LimitDeclaration } from '../types/entitlements';
|
|
2
|
+
/**
|
|
3
|
+
* The 4-layer limit-declaration merge, extracted so the browser AccessProvider
|
|
4
|
+
* and the manifest derivation (`fayz manifest emit`) resolve the EXACT same
|
|
5
|
+
* final set: core built-ins < entity-derived < plugin `declaredLimits` < app
|
|
6
|
+
* overrides. Later layers win by key. Pure: pass the layers in, get the merge
|
|
7
|
+
* out — deriving a declaration FROM an entity stays at the call site.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mergeLimitDeclarations(...layers: Array<LimitDeclaration[] | undefined>): LimitDeclaration[];
|
|
10
|
+
//# sourceMappingURL=limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/access/limits.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAE7D;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,MAAM,EAAE,KAAK,CAAC,gBAAgB,EAAE,GAAG,SAAS,CAAC,GAC/C,gBAAgB,EAAE,CAMpB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An address as a postal service knows it: everything except the parts only the
|
|
3
|
+
* resident can supply (number, complement).
|
|
4
|
+
*
|
|
5
|
+
* Field names mirror ShippingAddressInput in @fayz-ai/shop so the value crosses
|
|
6
|
+
* from lookup to checkout to the order with no translation step in between.
|
|
7
|
+
*/
|
|
8
|
+
export interface PostalAddress {
|
|
9
|
+
postalCode: string;
|
|
10
|
+
street: string;
|
|
11
|
+
district: string;
|
|
12
|
+
city: string;
|
|
13
|
+
state: string;
|
|
14
|
+
country: string;
|
|
15
|
+
/** IBGE municipality code when the source provides one — useful for tax later. */
|
|
16
|
+
cityCode?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Where a postal code is resolved. Implementations must not throw for a
|
|
19
|
+
* well-formed code that simply does not exist — that is `null`, not an error. */
|
|
20
|
+
export interface PostalLookupProvider {
|
|
21
|
+
lookup(postalCode: string): Promise<PostalAddress | null>;
|
|
22
|
+
}
|
|
23
|
+
/** Keep only digits. */
|
|
24
|
+
export declare function normalizePostalCode(value: string): string;
|
|
25
|
+
/** '01310100' | '01310-100' -> '01310-100'. Partial input formats progressively. */
|
|
26
|
+
export declare function formatPostalCode(value: string): string;
|
|
27
|
+
/** A Brazilian CEP is exactly eight digits. */
|
|
28
|
+
export declare function isValidPostalCode(value: string): boolean;
|
|
29
|
+
/** Drop the cache. Exposed for tests and for a "wrong address?" escape hatch. */
|
|
30
|
+
export declare function clearPostalCache(): void;
|
|
31
|
+
/**
|
|
32
|
+
* ViaCEP (https://viacep.com.br). Free, no key, CORS-enabled.
|
|
33
|
+
*
|
|
34
|
+
* The trap: an unknown CEP does NOT come back as 404. ViaCEP answers HTTP 200
|
|
35
|
+
* with `{"erro": true}` (and, on some deploys, the string "true"), so checking
|
|
36
|
+
* response.ok alone yields an address of empty strings that silently overwrites
|
|
37
|
+
* whatever the shopper had typed. Both shapes are treated as "not found".
|
|
38
|
+
*/
|
|
39
|
+
export declare function createViaCepProvider(options?: {
|
|
40
|
+
fetcher?: typeof fetch;
|
|
41
|
+
}): PostalLookupProvider;
|
|
42
|
+
export declare function setPostalLookupProvider(provider: PostalLookupProvider | null): void;
|
|
43
|
+
export declare function getPostalLookupProvider(): PostalLookupProvider;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a postal code to an address, through the cache.
|
|
46
|
+
*
|
|
47
|
+
* Returns null for a code that is malformed or does not exist; throws only when
|
|
48
|
+
* the lookup itself failed (offline, provider down), so a caller can tell
|
|
49
|
+
* "no such CEP" from "we could not check right now" and say the right thing.
|
|
50
|
+
*/
|
|
51
|
+
export declare function lookupPostalCode(postalCode: string): Promise<PostalAddress | null>;
|
|
52
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/address/index.ts"],"names":[],"mappings":"AAcA;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;kFACkF;AAClF,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAA;CAC1D;AAED,wBAAwB;AACxB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAExD;AA2CD,iFAAiF;AACjF,wBAAgB,gBAAgB,IAAI,IAAI,CAOvC;AAgBD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,KAAK,CAAA;CAAE,GAAG,oBAAoB,CAwB/F;AAUD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,GAAG,IAAI,CAEnF;AAED,wBAAgB,uBAAuB,IAAI,oBAAoB,CAG9D;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAUxF"}
|
|
@@ -22,5 +22,5 @@ function deriveEntityKey(entityDef) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export { clearEntityRegistry, deriveEntityKey, getAllEntities, getEntityByKey, registerEntity };
|
|
25
|
-
//# sourceMappingURL=chunk-
|
|
26
|
-
//# sourceMappingURL=chunk-
|
|
25
|
+
//# sourceMappingURL=chunk-CU64QI4A.js.map
|
|
26
|
+
//# sourceMappingURL=chunk-CU64QI4A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/entity/registry.ts"],"names":[],"mappings":";AAuBA,IAAM,cAAA,uBAAqB,GAAA,EAA8B;AAElD,SAAS,eAAe,KAAA,EAA+B;AAC5D,EAAA,cAAA,CAAe,GAAA,CAAI,KAAA,CAAM,SAAA,EAAW,KAAK,CAAA;AAC3C;AAEO,SAAS,eAAe,GAAA,EAA2C;AACxE,EAAA,OAAO,cAAA,CAAe,IAAI,GAAG,CAAA;AAC/B;AAEO,SAAS,cAAA,GAAqC;AACnD,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,cAAA,CAAe,MAAA,EAAQ,CAAA;AAC3C;AAEO,SAAS,mBAAA,GAA4B;AAC1C,EAAA,cAAA,CAAe,KAAA,EAAM;AACvB;AAEO,SAAS,gBAAgB,SAAA,EAA8B;AAC5D,EAAA,MAAM,OAAO,SAAA,CAAU,IAAA,CAAK,aAAY,CAAE,OAAA,CAAQ,QAAQ,GAAG,CAAA;AAC7D,EAAA,IAAI,SAAA,CAAU,IAAA,EAAM,SAAA,IAAa,SAAA,CAAU,MAAM,aAAA,EAAe;AAC9D,IAAA,OAAO,GAAG,SAAA,CAAU,IAAA,CAAK,SAAS,CAAA,CAAA,EAAI,SAAA,CAAU,KAAK,aAAa,CAAA,CAAA;AAAA,EACpE;AACA,EAAA,IAAI,SAAA,CAAU,IAAA,EAAM,SAAA,EAAW,OAAO,UAAU,IAAA,CAAK,SAAA;AACrD,EAAA,OAAO,IAAA;AACT","file":"chunk-CU64QI4A.js","sourcesContent":["import type { EntityDef } from '../types/crud'\nimport type { EntityArchetype } from '../types/entities'\n\nexport interface RegisteredEntity {\n entityKey: string\n label: string\n labelPlural: string\n icon?: string\n fields: EntityDef['fields']\n source: 'app' | 'plugin'\n /**\n * The definition itself, so consumers that need to *read* the entity — the AI\n * tool executor resolving a data provider — are not limited to the display\n * metadata. Optional: registrations made before this existed omit it.\n */\n entityDef?: EntityDef\n mockData?: Array<{ id: string }>\n archetype?: EntityArchetype\n pluginId?: string\n pluginName?: string\n pluginIcon?: string\n}\n\nconst entityRegistry = new Map<string, RegisteredEntity>()\n\nexport function registerEntity(entry: RegisteredEntity): void {\n entityRegistry.set(entry.entityKey, entry)\n}\n\nexport function getEntityByKey(key: string): RegisteredEntity | undefined {\n return entityRegistry.get(key)\n}\n\nexport function getAllEntities(): RegisteredEntity[] {\n return Array.from(entityRegistry.values())\n}\n\nexport function clearEntityRegistry(): void {\n entityRegistry.clear()\n}\n\nexport function deriveEntityKey(entityDef: EntityDef): string {\n const name = entityDef.name.toLowerCase().replace(/\\s+/g, '-')\n if (entityDef.data?.archetype && entityDef.data?.archetypeKind) {\n return `${entityDef.data.archetype}:${entityDef.data.archetypeKind}`\n }\n if (entityDef.data?.archetype) return entityDef.data.archetype\n return name\n}\n"]}
|
|
@@ -117,6 +117,142 @@ function createSupabaseProvider(table, config) {
|
|
|
117
117
|
}
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
|
+
|
|
121
|
+
// src/lib/cache.ts
|
|
122
|
+
function sortDeep(value) {
|
|
123
|
+
if (value === null || value === void 0 || typeof value !== "object") return value;
|
|
124
|
+
if (Array.isArray(value)) return value.map(sortDeep);
|
|
125
|
+
const sorted = {};
|
|
126
|
+
for (const k of Object.keys(value).sort()) {
|
|
127
|
+
sorted[k] = sortDeep(value[k]);
|
|
128
|
+
}
|
|
129
|
+
return sorted;
|
|
130
|
+
}
|
|
131
|
+
function stableKey(...parts) {
|
|
132
|
+
return parts.map((p) => typeof p === "string" ? p : JSON.stringify(sortDeep(p))).join(":");
|
|
133
|
+
}
|
|
134
|
+
function createCacheStore(config) {
|
|
135
|
+
const defaultTTL = config?.defaultTTL ?? 6e4;
|
|
136
|
+
const maxEntries = config?.maxEntries ?? 500;
|
|
137
|
+
const entries = /* @__PURE__ */ new Map();
|
|
138
|
+
function evictLRU() {
|
|
139
|
+
if (entries.size <= maxEntries) return;
|
|
140
|
+
let oldestKey = null;
|
|
141
|
+
let oldestAccess = Infinity;
|
|
142
|
+
for (const [key, entry] of entries) {
|
|
143
|
+
if (entry.lastAccess < oldestAccess) {
|
|
144
|
+
oldestAccess = entry.lastAccess;
|
|
145
|
+
oldestKey = key;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (oldestKey) entries.delete(oldestKey);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
get(key) {
|
|
152
|
+
const entry = entries.get(key);
|
|
153
|
+
if (!entry) return void 0;
|
|
154
|
+
if (Date.now() > entry.expiresAt) {
|
|
155
|
+
entries.delete(key);
|
|
156
|
+
return void 0;
|
|
157
|
+
}
|
|
158
|
+
entry.lastAccess = Date.now();
|
|
159
|
+
return entry.data;
|
|
160
|
+
},
|
|
161
|
+
set(key, data, ttl) {
|
|
162
|
+
const now = Date.now();
|
|
163
|
+
entries.set(key, {
|
|
164
|
+
data,
|
|
165
|
+
expiresAt: now + (ttl ?? defaultTTL),
|
|
166
|
+
lastAccess: now
|
|
167
|
+
});
|
|
168
|
+
evictLRU();
|
|
169
|
+
},
|
|
170
|
+
invalidate(prefix) {
|
|
171
|
+
for (const key of entries.keys()) {
|
|
172
|
+
if (key.startsWith(prefix)) {
|
|
173
|
+
entries.delete(key);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
clear() {
|
|
178
|
+
entries.clear();
|
|
179
|
+
},
|
|
180
|
+
size() {
|
|
181
|
+
return entries.size;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
var globalCache = createCacheStore();
|
|
186
|
+
function clearGlobalCache() {
|
|
187
|
+
globalCache.clear();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/tenant/index.ts
|
|
191
|
+
var _activeTenantId;
|
|
192
|
+
function setActiveTenantId(id) {
|
|
193
|
+
if (id !== _activeTenantId) {
|
|
194
|
+
_activeTenantId = id;
|
|
195
|
+
clearGlobalCache();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function getActiveTenantId() {
|
|
199
|
+
return _activeTenantId;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/data/count.ts
|
|
203
|
+
var TTL_MS = 15e3;
|
|
204
|
+
var cache = /* @__PURE__ */ new Map();
|
|
205
|
+
function cacheKey(table, tenantId, kind, period) {
|
|
206
|
+
return `${tenantId}::${table}::${kind ?? ""}::${period}`;
|
|
207
|
+
}
|
|
208
|
+
function startOfMonthISO() {
|
|
209
|
+
const now = /* @__PURE__ */ new Date();
|
|
210
|
+
return new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
|
|
211
|
+
}
|
|
212
|
+
var COUNT_TIMEOUT_MS = 5e3;
|
|
213
|
+
async function countByTenant(table, options = {}) {
|
|
214
|
+
const tenantId = options.tenantId ?? getActiveTenantId();
|
|
215
|
+
if (!tenantId) return 0;
|
|
216
|
+
const period = options.period ?? "total";
|
|
217
|
+
const key = cacheKey(table, tenantId, options.kind, period);
|
|
218
|
+
if (!options.fresh) {
|
|
219
|
+
const hit = cache.get(key);
|
|
220
|
+
if (hit && hit.expires > Date.now()) return hit.value;
|
|
221
|
+
}
|
|
222
|
+
const client = getSupabaseClientOptional();
|
|
223
|
+
if (!client) return 0;
|
|
224
|
+
let query = client.from(table).select("*", { count: "exact", head: true }).eq("tenant_id", tenantId);
|
|
225
|
+
if (options.kind) {
|
|
226
|
+
query = query.eq("kind", options.kind);
|
|
227
|
+
}
|
|
228
|
+
if (period === "month") {
|
|
229
|
+
query = query.gte("created_at", startOfMonthISO());
|
|
230
|
+
}
|
|
231
|
+
let timer;
|
|
232
|
+
const timeout = new Promise((resolve) => {
|
|
233
|
+
timer = setTimeout(() => resolve({ count: null, error: "timeout", timedOut: true }), COUNT_TIMEOUT_MS);
|
|
234
|
+
});
|
|
235
|
+
const settled = await Promise.race([
|
|
236
|
+
query,
|
|
237
|
+
timeout
|
|
238
|
+
]);
|
|
239
|
+
if (timer) clearTimeout(timer);
|
|
240
|
+
const { count, error } = settled;
|
|
241
|
+
const value = error ? 0 : count ?? 0;
|
|
242
|
+
if (!settled.timedOut) {
|
|
243
|
+
cache.set(key, { value, expires: Date.now() + TTL_MS });
|
|
244
|
+
}
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
function invalidateCount(tenantOrKey) {
|
|
248
|
+
if (!tenantOrKey) {
|
|
249
|
+
cache.clear();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
for (const key of cache.keys()) {
|
|
253
|
+
if (key.includes(tenantOrKey)) cache.delete(key);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
120
256
|
var FAYZ_CLOUD_URL = "https://yfxutrkyhydgltakbqle.supabase.co";
|
|
121
257
|
var FAYZ_CLOUD_PUBLISHABLE_KEY = "sb_publishable_v8rbvLDgnq5IbkVB5B6LxQ_D62mfPZX";
|
|
122
258
|
var _client = null;
|
|
@@ -481,75 +617,6 @@ function createArchetypeProvider(config) {
|
|
|
481
617
|
};
|
|
482
618
|
}
|
|
483
619
|
|
|
484
|
-
// src/lib/cache.ts
|
|
485
|
-
function sortDeep(value) {
|
|
486
|
-
if (value === null || value === void 0 || typeof value !== "object") return value;
|
|
487
|
-
if (Array.isArray(value)) return value.map(sortDeep);
|
|
488
|
-
const sorted = {};
|
|
489
|
-
for (const k of Object.keys(value).sort()) {
|
|
490
|
-
sorted[k] = sortDeep(value[k]);
|
|
491
|
-
}
|
|
492
|
-
return sorted;
|
|
493
|
-
}
|
|
494
|
-
function stableKey(...parts) {
|
|
495
|
-
return parts.map((p) => typeof p === "string" ? p : JSON.stringify(sortDeep(p))).join(":");
|
|
496
|
-
}
|
|
497
|
-
function createCacheStore(config) {
|
|
498
|
-
const defaultTTL = config?.defaultTTL ?? 6e4;
|
|
499
|
-
const maxEntries = config?.maxEntries ?? 500;
|
|
500
|
-
const entries = /* @__PURE__ */ new Map();
|
|
501
|
-
function evictLRU() {
|
|
502
|
-
if (entries.size <= maxEntries) return;
|
|
503
|
-
let oldestKey = null;
|
|
504
|
-
let oldestAccess = Infinity;
|
|
505
|
-
for (const [key, entry] of entries) {
|
|
506
|
-
if (entry.lastAccess < oldestAccess) {
|
|
507
|
-
oldestAccess = entry.lastAccess;
|
|
508
|
-
oldestKey = key;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
if (oldestKey) entries.delete(oldestKey);
|
|
512
|
-
}
|
|
513
|
-
return {
|
|
514
|
-
get(key) {
|
|
515
|
-
const entry = entries.get(key);
|
|
516
|
-
if (!entry) return void 0;
|
|
517
|
-
if (Date.now() > entry.expiresAt) {
|
|
518
|
-
entries.delete(key);
|
|
519
|
-
return void 0;
|
|
520
|
-
}
|
|
521
|
-
entry.lastAccess = Date.now();
|
|
522
|
-
return entry.data;
|
|
523
|
-
},
|
|
524
|
-
set(key, data, ttl) {
|
|
525
|
-
const now = Date.now();
|
|
526
|
-
entries.set(key, {
|
|
527
|
-
data,
|
|
528
|
-
expiresAt: now + (ttl ?? defaultTTL),
|
|
529
|
-
lastAccess: now
|
|
530
|
-
});
|
|
531
|
-
evictLRU();
|
|
532
|
-
},
|
|
533
|
-
invalidate(prefix) {
|
|
534
|
-
for (const key of entries.keys()) {
|
|
535
|
-
if (key.startsWith(prefix)) {
|
|
536
|
-
entries.delete(key);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
},
|
|
540
|
-
clear() {
|
|
541
|
-
entries.clear();
|
|
542
|
-
},
|
|
543
|
-
size() {
|
|
544
|
-
return entries.size;
|
|
545
|
-
}
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
var globalCache = createCacheStore();
|
|
549
|
-
function clearGlobalCache() {
|
|
550
|
-
globalCache.clear();
|
|
551
|
-
}
|
|
552
|
-
|
|
553
620
|
// src/data/cached.ts
|
|
554
621
|
function withCache(provider, options) {
|
|
555
622
|
function prefix() {
|
|
@@ -581,18 +648,6 @@ function withCache(provider, options) {
|
|
|
581
648
|
};
|
|
582
649
|
}
|
|
583
650
|
|
|
584
|
-
// src/tenant/index.ts
|
|
585
|
-
var _activeTenantId;
|
|
586
|
-
function setActiveTenantId(id) {
|
|
587
|
-
if (id !== _activeTenantId) {
|
|
588
|
-
_activeTenantId = id;
|
|
589
|
-
clearGlobalCache();
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
function getActiveTenantId() {
|
|
593
|
-
return _activeTenantId;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
651
|
// src/data/resolve.ts
|
|
597
652
|
function resolveDataProvider(entityDef, mockData, options = {}) {
|
|
598
653
|
const backend = options.backend;
|
|
@@ -626,7 +681,7 @@ function resolveDataProvider(entityDef, mockData, options = {}) {
|
|
|
626
681
|
if (client && entityDef.data?.table) {
|
|
627
682
|
const tenantId = () => getActiveTenantId();
|
|
628
683
|
const cacheOptions = {
|
|
629
|
-
table: entityDef.data.table,
|
|
684
|
+
table: entityDef.data.archetypeKind ? `${entityDef.data.table}#${entityDef.data.archetypeKind}` : entityDef.data.table,
|
|
630
685
|
tenantId,
|
|
631
686
|
ttl: entityDef.data.cacheTTL
|
|
632
687
|
};
|
|
@@ -661,6 +716,7 @@ function resolveDataProvider(entityDef, mockData, options = {}) {
|
|
|
661
716
|
exports.FAYZ_CLOUD_PUBLISHABLE_KEY = FAYZ_CLOUD_PUBLISHABLE_KEY;
|
|
662
717
|
exports.FAYZ_CLOUD_URL = FAYZ_CLOUD_URL;
|
|
663
718
|
exports.clearGlobalCache = clearGlobalCache;
|
|
719
|
+
exports.countByTenant = countByTenant;
|
|
664
720
|
exports.createArchetypeProvider = createArchetypeProvider;
|
|
665
721
|
exports.createCacheStore = createCacheStore;
|
|
666
722
|
exports.createFayzApiProvider = createFayzApiProvider;
|
|
@@ -670,10 +726,11 @@ exports.getActiveTenantId = getActiveTenantId;
|
|
|
670
726
|
exports.getFayzCloudClient = getFayzCloudClient;
|
|
671
727
|
exports.getSupabaseClientOptional = getSupabaseClientOptional;
|
|
672
728
|
exports.globalCache = globalCache;
|
|
729
|
+
exports.invalidateCount = invalidateCount;
|
|
673
730
|
exports.resolveDataProvider = resolveDataProvider;
|
|
674
731
|
exports.setActiveTenantId = setActiveTenantId;
|
|
675
732
|
exports.setGlobalSupabaseClient = setGlobalSupabaseClient;
|
|
676
733
|
exports.stableKey = stableKey;
|
|
677
734
|
exports.withCache = withCache;
|
|
678
|
-
//# sourceMappingURL=chunk-
|
|
679
|
-
//# sourceMappingURL=chunk-
|
|
735
|
+
//# sourceMappingURL=chunk-CYAWDW44.cjs.map
|
|
736
|
+
//# sourceMappingURL=chunk-CYAWDW44.cjs.map
|