Feature Policies API + Invitations Policies (#375)

- Added Feature Policy API: a declarative system to enable/disable/modify default behavior in the SaaS kit
- Team invitation policies with pre-checks using the Feature Policy API: Invite Members dialog now shows loading, errors, and clear reasons when invitations are blocked
- Version bump to 2.16.0 and widespread dependency updates (Supabase, React types, react-i18next, etc.).
- Added comprehensive docs for the new policy system and orchestrators.
- Subscription cancellations now trigger immediate invoicing explicitly
This commit is contained in:
Giancarlo Buomprisco
2025-09-30 12:36:19 +08:00
committed by GitHub
parent 3c13b5ec1e
commit 1dd6fdad22
53 changed files with 3908 additions and 1128 deletions

View File

@@ -0,0 +1,81 @@
import { createRegistry } from '@kit/shared/registry';
import type { FeaturePolicyDefinition } from './declarative';
import type { PolicyContext } from './types';
/**
* Simple policy registry interface
*/
export interface PolicyRegistry {
/** Register a single policy definition */
registerPolicy<
TContext extends PolicyContext = PolicyContext,
TConfig = unknown,
>(
definition: FeaturePolicyDefinition<TContext, TConfig>,
): PolicyRegistry;
/** Get a policy definition by ID */
getPolicy<TContext extends PolicyContext = PolicyContext, TConfig = unknown>(
id: string,
): Promise<FeaturePolicyDefinition<TContext, TConfig>>;
/** Check if a policy exists */
hasPolicy(id: string): boolean;
/** List all registered policy IDs */
listPolicies(): string[];
}
/**
* Creates a new policy registry instance
*/
export function createPolicyRegistry(): PolicyRegistry {
const baseRegistry = createRegistry<
FeaturePolicyDefinition<PolicyContext, unknown>,
string
>();
const policyIds = new Set<string>();
return {
registerPolicy<
TContext extends PolicyContext = PolicyContext,
TConfig = unknown,
>(definition: FeaturePolicyDefinition<TContext, TConfig>) {
// Check for duplicates
if (policyIds.has(definition.id)) {
throw new Error(
`Policy with ID "${definition.id}" is already registered`,
);
}
// Register the policy definition
baseRegistry.register(definition.id, () => definition);
policyIds.add(definition.id);
return this;
},
async getPolicy<
TContext extends PolicyContext = PolicyContext,
TConfig = unknown,
>(id: string) {
if (!policyIds.has(id)) {
throw new Error(`Policy with ID "${id}" is not registered`);
}
return baseRegistry.get(id) as Promise<
FeaturePolicyDefinition<TContext, TConfig>
>;
},
hasPolicy(id: string) {
return policyIds.has(id);
},
listPolicies() {
return Array.from(policyIds);
},
};
}