From 2fdef92d620f851c3682e9848ecd765cfccdf274 Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Fri, 12 Sep 2025 15:57:41 +0200 Subject: [PATCH] Refactor feature flag utilities to inject `Payload` instance, add strict types, and update `.npmignore` settings --- .npmignore | 50 ++++++++++++++++++++++++++ package.json | 3 +- src/endpoints/customEndpointHandler.ts | 2 +- src/hooks/server.ts | 48 ++++++++++++++----------- src/index.ts | 10 +++--- 5 files changed, 85 insertions(+), 28 deletions(-) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..5ff90b1 --- /dev/null +++ b/.npmignore @@ -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 \ No newline at end of file diff --git a/package.json b/package.json index 0dc958d..9074b39 100644 --- a/package.json +++ b/package.json @@ -116,5 +116,6 @@ ] }, "registry": "https://registry.npmjs.org/", - "dependencies": {} + "dependencies": {}, + "packageManager": "pnpm@10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184" } diff --git a/src/endpoints/customEndpointHandler.ts b/src/endpoints/customEndpointHandler.ts index eaa5135..83db3d0 100644 --- a/src/endpoints/customEndpointHandler.ts +++ b/src/endpoints/customEndpointHandler.ts @@ -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] diff --git a/src/hooks/server.ts b/src/hooks/server.ts index 18b211f..79e016b 100644 --- a/src/hooks/server.ts +++ b/src/hooks/server.ts @@ -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 { - 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 { /** * Get a specific feature flag by name (for use in React Server Components) */ -export async function getFeatureFlag(flagName: string): Promise { +export async function getFeatureFlag(flagName: string, payload?: Payload): Promise { 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 { - const flag = await getFeatureFlag(flagName) +export async function isFeatureEnabled(flagName: string, payload?: Payload): Promise { + 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> { +export async function getAllFeatureFlags(payload?: Payload): Promise> { 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> */ export async function isUserInRollout( flagName: string, - userId: string + userId: string, + payload?: Payload ): Promise { - 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 { - 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 { +export async function getFeatureFlagsByTag(tag: string, payload?: Payload): Promise { 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, diff --git a/src/index.ts b/src/index.ts index ed4173f..d371eac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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', },