Refactor feature flag utilities to inject Payload instance, add strict types, and update .npmignore settings

This commit is contained in:
2025-09-12 15:57:41 +02:00
parent 48834c6fa2
commit 2fdef92d62
5 changed files with 85 additions and 28 deletions

50
.npmignore Normal file
View File

@@ -0,0 +1,50 @@
# Source files
src/
dev/
.next/
.turbo/
# Development files
*.log
.env*
.DS_Store
*.tsbuildinfo
.turbo
.cache
.temp
.tmp
# Test files
*.test.*
*.spec.*
e2e/
tests/
__tests__/
# Development dependencies
node_modules/
coverage/
# Build configs
.swcrc
tsconfig.json
vitest.config.ts
playwright.config.ts
next.config.mjs
eslint.config.js
# Documentation (keep README.md)
docs/
.github/
# IDE
.vscode/
.idea/
*.swp
*.swo
# Only include built files
!dist/
!package.json
!README.md
!LICENSE

View File

@@ -116,5 +116,6 @@
]
},
"registry": "https://registry.npmjs.org/",
"dependencies": {}
"dependencies": {},
"packageManager": "pnpm@10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184"
}

View File

@@ -3,7 +3,7 @@ import type { PayloadHandler } from 'payload'
export const customEndpointHandler = (collectionSlug: string): PayloadHandler =>
async (req) => {
const { payload } = req
const url = new URL(req.url)
const url = new URL(req.url || '')
const pathParts = url.pathname.split('/').filter(Boolean)
const flagName = pathParts[pathParts.length - 1]

View File

@@ -1,5 +1,4 @@
import { getPayload } from 'payload'
import configPromise from '@payload-config'
import { getPayload, type Payload } from 'payload'
export interface FeatureFlag {
name: string
@@ -14,16 +13,15 @@ export interface FeatureFlag {
}
// Helper to get the collection slug from config
async function getCollectionSlug(): Promise<string> {
const payload = await getPayload({ config: configPromise })
function getCollectionSlug(payload: Payload): string {
// Look for the feature flags collection - it should have a 'name' field with unique constraint
const collection = payload.config.collections?.find(col =>
col.fields.some(field =>
col.fields.some((field: any) =>
field.name === 'name' &&
field.type === 'text' &&
field.unique === true
) &&
col.fields.some(field => field.name === 'enabled' && field.type === 'checkbox')
col.fields.some((field: any) => field.name === 'enabled' && field.type === 'checkbox')
)
return collection?.slug || 'feature-flags'
}
@@ -31,10 +29,12 @@ async function getCollectionSlug(): Promise<string> {
/**
* Get a specific feature flag by name (for use in React Server Components)
*/
export async function getFeatureFlag(flagName: string): Promise<FeatureFlag | null> {
export async function getFeatureFlag(flagName: string, payload?: Payload): Promise<FeatureFlag | null> {
try {
const payload = await getPayload({ config: configPromise })
const collectionSlug = await getCollectionSlug()
if (!payload) {
throw new Error('Payload instance is required')
}
const collectionSlug = getCollectionSlug(payload)
const result = await payload.find({
collection: collectionSlug,
@@ -68,18 +68,20 @@ export async function getFeatureFlag(flagName: string): Promise<FeatureFlag | nu
/**
* Check if a feature flag is enabled (for use in React Server Components)
*/
export async function isFeatureEnabled(flagName: string): Promise<boolean> {
const flag = await getFeatureFlag(flagName)
export async function isFeatureEnabled(flagName: string, payload?: Payload): Promise<boolean> {
const flag = await getFeatureFlag(flagName, payload)
return flag?.enabled ?? false
}
/**
* Get all active feature flags (for use in React Server Components)
*/
export async function getAllFeatureFlags(): Promise<Record<string, FeatureFlag>> {
export async function getAllFeatureFlags(payload?: Payload): Promise<Record<string, FeatureFlag>> {
try {
const payload = await getPayload({ config: configPromise })
const collectionSlug = await getCollectionSlug()
if (!payload) {
throw new Error('Payload instance is required')
}
const collectionSlug = getCollectionSlug(payload)
const result = await payload.find({
collection: collectionSlug,
@@ -115,9 +117,10 @@ export async function getAllFeatureFlags(): Promise<Record<string, FeatureFlag>>
*/
export async function isUserInRollout(
flagName: string,
userId: string
userId: string,
payload?: Payload
): Promise<boolean> {
const flag = await getFeatureFlag(flagName)
const flag = await getFeatureFlag(flagName, payload)
if (!flag?.enabled) {
return false
@@ -140,9 +143,10 @@ export async function isUserInRollout(
*/
export async function getUserVariant(
flagName: string,
userId: string
userId: string,
payload?: Payload
): Promise<string | null> {
const flag = await getFeatureFlag(flagName)
const flag = await getFeatureFlag(flagName, payload)
if (!flag?.enabled || !flag.variants || flag.variants.length === 0) {
return null
@@ -169,10 +173,12 @@ export async function getUserVariant(
/**
* Get feature flags by tags (for use in React Server Components)
*/
export async function getFeatureFlagsByTag(tag: string): Promise<FeatureFlag[]> {
export async function getFeatureFlagsByTag(tag: string, payload?: Payload): Promise<FeatureFlag[]> {
try {
const payload = await getPayload({ config: configPromise })
const collectionSlug = await getCollectionSlug()
if (!payload) {
throw new Error('Payload instance is required')
}
const collectionSlug = getCollectionSlug(payload)
const result = await payload.find({
collection: collectionSlug,

View File

@@ -89,7 +89,7 @@ export const payloadFeatureFlags =
...(enableRollouts ? [
{
name: 'rolloutPercentage',
type: 'number',
type: 'number' as const,
min: 0,
max: 100,
defaultValue: 100,
@@ -102,7 +102,7 @@ export const payloadFeatureFlags =
...(enableVariants ? [
{
name: 'variants',
type: 'array',
type: 'array' as const,
admin: {
description: 'Define variants for A/B testing',
condition: (data: any) => data?.enabled === true,
@@ -110,7 +110,7 @@ export const payloadFeatureFlags =
fields: [
{
name: 'name',
type: 'text',
type: 'text' as const,
required: true,
admin: {
description: 'Variant identifier (e.g., control, variant-a)',
@@ -118,7 +118,7 @@ export const payloadFeatureFlags =
},
{
name: 'weight',
type: 'number',
type: 'number' as const,
min: 0,
max: 100,
required: true,
@@ -128,7 +128,7 @@ export const payloadFeatureFlags =
},
{
name: 'metadata',
type: 'json',
type: 'json' as const,
admin: {
description: 'Additional data for this variant',
},