@geejay/use-feature-flags 1.0.12 → 1.0.17

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/index.cjs ADDED
@@ -0,0 +1,131 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jotai = require('jotai');
5
+ var supabaseJs = require('@supabase/supabase-js');
6
+
7
+ // src/useFeatureFlags.ts
8
+ var featureFlagsAtom = jotai.atom({
9
+ flags: [],
10
+ loading: true
11
+ });
12
+ var apiKey = null;
13
+ function setApiKey(key) {
14
+ apiKey = key;
15
+ }
16
+ function getApiKey() {
17
+ return apiKey;
18
+ }
19
+ var DEFAULT_SUPABASE_URL = "https://khppgsehvvlukzfdqbuo.supabase.co";
20
+ var DEFAULT_SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk";
21
+ var _supabase = null;
22
+ function getSupabase() {
23
+ if (_supabase) return _supabase;
24
+ _supabase = supabaseJs.createClient(DEFAULT_SUPABASE_URL, DEFAULT_SUPABASE_KEY, {
25
+ auth: {
26
+ persistSession: false,
27
+ autoRefreshToken: false,
28
+ storageKey: "feature-flags"
29
+ }
30
+ });
31
+ return _supabase;
32
+ }
33
+
34
+ // src/useFeatureFlags.ts
35
+ var EDGE_FN_URL = `${DEFAULT_SUPABASE_URL}/functions/v1/get-feature-flags`;
36
+ var initialized = false;
37
+ function useFeatureFlags(passedKey, environment = window.location.hostname || "localhost") {
38
+ const sanitizedEnvironment = react.useMemo(
39
+ () => environment.replace(/:\d+$/, ""),
40
+ [environment]
41
+ );
42
+ const [state, setState] = jotai.useAtom(featureFlagsAtom);
43
+ const [envId, setEnvId] = react.useState(null);
44
+ const debounceTimeout = react.useRef(null);
45
+ const cleanupRef = react.useRef(null);
46
+ const apiKey2 = passedKey;
47
+ react.useEffect(() => {
48
+ console.log(
49
+ "[use-feature-flags] initializing",
50
+ `environment: ${sanitizedEnvironment}`,
51
+ `apiKey provided: ${Boolean(apiKey2)}`
52
+ );
53
+ }, [sanitizedEnvironment, apiKey2]);
54
+ const supabase = getSupabase();
55
+ const fetchFlags = async () => {
56
+ setState((prev) => ({ ...prev, loading: true }));
57
+ console.log("[use-feature-flags] fetching flags for", sanitizedEnvironment);
58
+ try {
59
+ const res = await fetch(EDGE_FN_URL, {
60
+ method: "POST",
61
+ headers: {
62
+ "Content-Type": "application/json",
63
+ ...apiKey2 ? { "api-key": apiKey2 } : {}
64
+ },
65
+ body: JSON.stringify({ environment: sanitizedEnvironment })
66
+ });
67
+ const json = await res.json();
68
+ if (!res.ok) {
69
+ console.warn("Edge function error:", (json == null ? void 0 : json.error) || res.statusText);
70
+ setState({ flags: [], loading: false });
71
+ return;
72
+ }
73
+ const flags = json.flags || [];
74
+ setState({ flags, loading: false });
75
+ console.log("[use-feature-flags] fetched flags", flags);
76
+ if (flags.length > 0 && flags[0].environment_id) {
77
+ setEnvId(flags[0].environment_id);
78
+ }
79
+ } catch (err) {
80
+ console.error("Error fetching flags:", err.message);
81
+ setState({ flags: [], loading: false });
82
+ }
83
+ };
84
+ react.useEffect(() => {
85
+ if (!apiKey2 || initialized) return;
86
+ initialized = true;
87
+ fetchFlags();
88
+ return () => {
89
+ if (debounceTimeout.current) clearTimeout(debounceTimeout.current);
90
+ if (cleanupRef.current) cleanupRef.current();
91
+ };
92
+ }, [sanitizedEnvironment, apiKey2]);
93
+ react.useEffect(() => {
94
+ if (!envId) return;
95
+ const channel = supabase.channel(`flags-${sanitizedEnvironment}`).on(
96
+ "postgres_changes",
97
+ {
98
+ event: "*",
99
+ schema: "public",
100
+ table: "feature_flags",
101
+ filter: `environment_id=eq.${envId}`
102
+ },
103
+ () => {
104
+ if (debounceTimeout.current) clearTimeout(debounceTimeout.current);
105
+ debounceTimeout.current = setTimeout(fetchFlags, 300);
106
+ }
107
+ ).subscribe();
108
+ if (cleanupRef.current) cleanupRef.current();
109
+ cleanupRef.current = () => supabase.removeChannel(channel);
110
+ return () => {
111
+ if (debounceTimeout.current) clearTimeout(debounceTimeout.current);
112
+ supabase.removeChannel(channel);
113
+ };
114
+ }, [envId]);
115
+ return {
116
+ isActive: (key) => {
117
+ const active = state.flags.some((f) => f.key === key && f.enabled === true);
118
+ console.log("[use-feature-flags] isActive", key, active);
119
+ return active;
120
+ },
121
+ flags: state.flags,
122
+ loading: state.loading
123
+ };
124
+ }
125
+
126
+ exports.featureFlagsAtom = featureFlagsAtom;
127
+ exports.getApiKey = getApiKey;
128
+ exports.setApiKey = setApiKey;
129
+ exports.useFeatureFlags = useFeatureFlags;
130
+ //# sourceMappingURL=index.cjs.map
131
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/store.ts","../src/supabaseClient.ts","../src/useFeatureFlags.ts"],"names":["atom","createClient","useMemo","useAtom","useState","useRef","apiKey","useEffect"],"mappings":"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","file":"index.cjs","sourcesContent":["import { atom } from 'jotai';\n\nexport type FeatureFlag = {\n id: string;\n key: string;\n enabled: boolean;\n environment: string;\n};\n\nexport const featureFlagsAtom = atom<{ flags: FeatureFlag[]; loading: boolean }>({\n flags: [],\n loading: true,\n});\n\n// store.ts\nlet apiKey: string | null = null;\n\nexport function setApiKey(key: string) {\n apiKey = key;\n}\n\nexport function getApiKey() {\n return apiKey;\n}\n","import { createClient } from '@supabase/supabase-js';\n\nexport const DEFAULT_SUPABASE_URL = 'https://khppgsehvvlukzfdqbuo.supabase.co';\nexport const DEFAULT_SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk';\n\nlet _supabase: ReturnType<typeof createClient> | null = null;\n\nexport function getSupabase() {\n if (_supabase) return _supabase;\n _supabase = createClient(DEFAULT_SUPABASE_URL, DEFAULT_SUPABASE_KEY, {\n auth: {\n persistSession: false,\n autoRefreshToken: false,\n storageKey: 'feature-flags',\n },\n });\n return _supabase;\n}\n","\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { FeatureFlag, featureFlagsAtom } from './store';\nimport { useAtom } from 'jotai';\nimport { getSupabase, DEFAULT_SUPABASE_URL } from './supabaseClient';\n\nconst EDGE_FN_URL = `${DEFAULT_SUPABASE_URL}/functions/v1/get-feature-flags`;\n\n\n\n\ntype FlagState = {\n flags: FeatureFlag[];\n loading: boolean;\n};\n\nlet initialized = false;\n\nexport function useFeatureFlags(\n passedKey?: string,\n environment = window.location.hostname || 'localhost'\n) {\n const sanitizedEnvironment = useMemo(\n () => environment.replace(/:\\d+$/, ''),\n [environment]\n );\n const [state, setState] = useAtom(featureFlagsAtom);\n const [envId, setEnvId] = useState<number | null>(null);\n const debounceTimeout = useRef<NodeJS.Timeout | null>(null);\n const cleanupRef = useRef<(() => void) | null>(null);\n\n const apiKey = passedKey;\n\n useEffect(() => {\n console.log(\n '[use-feature-flags] initializing',\n `environment: ${sanitizedEnvironment}`,\n `apiKey provided: ${Boolean(apiKey)}`\n );\n }, [sanitizedEnvironment, apiKey]);\n const supabase = getSupabase();\n\n const fetchFlags = async () => {\n setState((prev) => ({ ...prev, loading: true }));\n\n console.log('[use-feature-flags] fetching flags for', sanitizedEnvironment);\n\n try {\n const res = await fetch(EDGE_FN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { 'api-key': apiKey } : {}),\n },\n body: JSON.stringify({ environment: sanitizedEnvironment }),\n });\n\n const json = await res.json();\n if (!res.ok) {\n console.warn('Edge function error:', json?.error || res.statusText);\n setState({ flags: [], loading: false });\n return;\n }\n\n const flags = json.flags || [];\n setState({ flags, loading: false });\n console.log('[use-feature-flags] fetched flags', flags);\n\n // Store environment_id from first flag (assumes all have same env)\n if (flags.length > 0 && flags[0].environment_id) {\n setEnvId(flags[0].environment_id);\n }\n } catch (err: any) {\n console.error('Error fetching flags:', err.message);\n setState({ flags: [], loading: false });\n }\n };\n\n useEffect(() => {\n if (!apiKey || initialized) return;\n initialized = true;\n\n fetchFlags();\n\n return () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n if (cleanupRef.current) cleanupRef.current();\n };\n }, [sanitizedEnvironment, apiKey]);\n\n // Subscribe to flag changes for the correct environment\n useEffect(() => {\n if (!envId) return;\n\n const channel = supabase\n .channel(`flags-${sanitizedEnvironment}`)\n .on(\n 'postgres_changes',\n {\n event: '*',\n schema: 'public',\n table: 'feature_flags',\n filter: `environment_id=eq.${envId}`,\n },\n () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n debounceTimeout.current = setTimeout(fetchFlags, 300);\n }\n )\n .subscribe();\n\n if (cleanupRef.current) cleanupRef.current();\n cleanupRef.current = () => supabase.removeChannel(channel);\n\n return () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n supabase.removeChannel(channel);\n };\n }, [envId]);\n\n return {\n isActive: (key: string) => {\n const active = state.flags.some((f) => f.key === key && f.enabled === true);\n console.log('[use-feature-flags] isActive', key, active);\n return active;\n },\n flags: state.flags,\n loading: state.loading,\n };\n}\n\n\n\n\n\n"]}
@@ -0,0 +1,27 @@
1
+ import * as jotai from 'jotai';
2
+
3
+ type FeatureFlag = {
4
+ id: string;
5
+ key: string;
6
+ enabled: boolean;
7
+ environment: string;
8
+ };
9
+ declare const featureFlagsAtom: jotai.PrimitiveAtom<{
10
+ flags: FeatureFlag[];
11
+ loading: boolean;
12
+ }> & {
13
+ init: {
14
+ flags: FeatureFlag[];
15
+ loading: boolean;
16
+ };
17
+ };
18
+ declare function setApiKey(key: string): void;
19
+ declare function getApiKey(): string | null;
20
+
21
+ declare function useFeatureFlags(passedKey?: string, environment?: string): {
22
+ isActive: (key: string) => boolean;
23
+ flags: FeatureFlag[];
24
+ loading: boolean;
25
+ };
26
+
27
+ export { type FeatureFlag, featureFlagsAtom, getApiKey, setApiKey, useFeatureFlags };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,27 @@
1
- export * from './useFeatureFlags';
2
- export * from './store';
1
+ import * as jotai from 'jotai';
2
+
3
+ type FeatureFlag = {
4
+ id: string;
5
+ key: string;
6
+ enabled: boolean;
7
+ environment: string;
8
+ };
9
+ declare const featureFlagsAtom: jotai.PrimitiveAtom<{
10
+ flags: FeatureFlag[];
11
+ loading: boolean;
12
+ }> & {
13
+ init: {
14
+ flags: FeatureFlag[];
15
+ loading: boolean;
16
+ };
17
+ };
18
+ declare function setApiKey(key: string): void;
19
+ declare function getApiKey(): string | null;
20
+
21
+ declare function useFeatureFlags(passedKey?: string, environment?: string): {
22
+ isActive: (key: string) => boolean;
23
+ flags: FeatureFlag[];
24
+ loading: boolean;
25
+ };
26
+
27
+ export { type FeatureFlag, featureFlagsAtom, getApiKey, setApiKey, useFeatureFlags };
package/dist/index.js CHANGED
@@ -1,2 +1,126 @@
1
- export * from './useFeatureFlags';
2
- export * from './store';
1
+ import { useMemo, useState, useRef, useEffect } from 'react';
2
+ import { atom, useAtom } from 'jotai';
3
+ import { createClient } from '@supabase/supabase-js';
4
+
5
+ // src/useFeatureFlags.ts
6
+ var featureFlagsAtom = atom({
7
+ flags: [],
8
+ loading: true
9
+ });
10
+ var apiKey = null;
11
+ function setApiKey(key) {
12
+ apiKey = key;
13
+ }
14
+ function getApiKey() {
15
+ return apiKey;
16
+ }
17
+ var DEFAULT_SUPABASE_URL = "https://khppgsehvvlukzfdqbuo.supabase.co";
18
+ var DEFAULT_SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk";
19
+ var _supabase = null;
20
+ function getSupabase() {
21
+ if (_supabase) return _supabase;
22
+ _supabase = createClient(DEFAULT_SUPABASE_URL, DEFAULT_SUPABASE_KEY, {
23
+ auth: {
24
+ persistSession: false,
25
+ autoRefreshToken: false,
26
+ storageKey: "feature-flags"
27
+ }
28
+ });
29
+ return _supabase;
30
+ }
31
+
32
+ // src/useFeatureFlags.ts
33
+ var EDGE_FN_URL = `${DEFAULT_SUPABASE_URL}/functions/v1/get-feature-flags`;
34
+ var initialized = false;
35
+ function useFeatureFlags(passedKey, environment = window.location.hostname || "localhost") {
36
+ const sanitizedEnvironment = useMemo(
37
+ () => environment.replace(/:\d+$/, ""),
38
+ [environment]
39
+ );
40
+ const [state, setState] = useAtom(featureFlagsAtom);
41
+ const [envId, setEnvId] = useState(null);
42
+ const debounceTimeout = useRef(null);
43
+ const cleanupRef = useRef(null);
44
+ const apiKey2 = passedKey;
45
+ useEffect(() => {
46
+ console.log(
47
+ "[use-feature-flags] initializing",
48
+ `environment: ${sanitizedEnvironment}`,
49
+ `apiKey provided: ${Boolean(apiKey2)}`
50
+ );
51
+ }, [sanitizedEnvironment, apiKey2]);
52
+ const supabase = getSupabase();
53
+ const fetchFlags = async () => {
54
+ setState((prev) => ({ ...prev, loading: true }));
55
+ console.log("[use-feature-flags] fetching flags for", sanitizedEnvironment);
56
+ try {
57
+ const res = await fetch(EDGE_FN_URL, {
58
+ method: "POST",
59
+ headers: {
60
+ "Content-Type": "application/json",
61
+ ...apiKey2 ? { "api-key": apiKey2 } : {}
62
+ },
63
+ body: JSON.stringify({ environment: sanitizedEnvironment })
64
+ });
65
+ const json = await res.json();
66
+ if (!res.ok) {
67
+ console.warn("Edge function error:", (json == null ? void 0 : json.error) || res.statusText);
68
+ setState({ flags: [], loading: false });
69
+ return;
70
+ }
71
+ const flags = json.flags || [];
72
+ setState({ flags, loading: false });
73
+ console.log("[use-feature-flags] fetched flags", flags);
74
+ if (flags.length > 0 && flags[0].environment_id) {
75
+ setEnvId(flags[0].environment_id);
76
+ }
77
+ } catch (err) {
78
+ console.error("Error fetching flags:", err.message);
79
+ setState({ flags: [], loading: false });
80
+ }
81
+ };
82
+ useEffect(() => {
83
+ if (!apiKey2 || initialized) return;
84
+ initialized = true;
85
+ fetchFlags();
86
+ return () => {
87
+ if (debounceTimeout.current) clearTimeout(debounceTimeout.current);
88
+ if (cleanupRef.current) cleanupRef.current();
89
+ };
90
+ }, [sanitizedEnvironment, apiKey2]);
91
+ useEffect(() => {
92
+ if (!envId) return;
93
+ const channel = supabase.channel(`flags-${sanitizedEnvironment}`).on(
94
+ "postgres_changes",
95
+ {
96
+ event: "*",
97
+ schema: "public",
98
+ table: "feature_flags",
99
+ filter: `environment_id=eq.${envId}`
100
+ },
101
+ () => {
102
+ if (debounceTimeout.current) clearTimeout(debounceTimeout.current);
103
+ debounceTimeout.current = setTimeout(fetchFlags, 300);
104
+ }
105
+ ).subscribe();
106
+ if (cleanupRef.current) cleanupRef.current();
107
+ cleanupRef.current = () => supabase.removeChannel(channel);
108
+ return () => {
109
+ if (debounceTimeout.current) clearTimeout(debounceTimeout.current);
110
+ supabase.removeChannel(channel);
111
+ };
112
+ }, [envId]);
113
+ return {
114
+ isActive: (key) => {
115
+ const active = state.flags.some((f) => f.key === key && f.enabled === true);
116
+ console.log("[use-feature-flags] isActive", key, active);
117
+ return active;
118
+ },
119
+ flags: state.flags,
120
+ loading: state.loading
121
+ };
122
+ }
123
+
124
+ export { featureFlagsAtom, getApiKey, setApiKey, useFeatureFlags };
125
+ //# sourceMappingURL=index.js.map
126
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/store.ts","../src/supabaseClient.ts","../src/useFeatureFlags.ts"],"names":["apiKey"],"mappings":"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","file":"index.js","sourcesContent":["import { atom } from 'jotai';\n\nexport type FeatureFlag = {\n id: string;\n key: string;\n enabled: boolean;\n environment: string;\n};\n\nexport const featureFlagsAtom = atom<{ flags: FeatureFlag[]; loading: boolean }>({\n flags: [],\n loading: true,\n});\n\n// store.ts\nlet apiKey: string | null = null;\n\nexport function setApiKey(key: string) {\n apiKey = key;\n}\n\nexport function getApiKey() {\n return apiKey;\n}\n","import { createClient } from '@supabase/supabase-js';\n\nexport const DEFAULT_SUPABASE_URL = 'https://khppgsehvvlukzfdqbuo.supabase.co';\nexport const DEFAULT_SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk';\n\nlet _supabase: ReturnType<typeof createClient> | null = null;\n\nexport function getSupabase() {\n if (_supabase) return _supabase;\n _supabase = createClient(DEFAULT_SUPABASE_URL, DEFAULT_SUPABASE_KEY, {\n auth: {\n persistSession: false,\n autoRefreshToken: false,\n storageKey: 'feature-flags',\n },\n });\n return _supabase;\n}\n","\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { FeatureFlag, featureFlagsAtom } from './store';\nimport { useAtom } from 'jotai';\nimport { getSupabase, DEFAULT_SUPABASE_URL } from './supabaseClient';\n\nconst EDGE_FN_URL = `${DEFAULT_SUPABASE_URL}/functions/v1/get-feature-flags`;\n\n\n\n\ntype FlagState = {\n flags: FeatureFlag[];\n loading: boolean;\n};\n\nlet initialized = false;\n\nexport function useFeatureFlags(\n passedKey?: string,\n environment = window.location.hostname || 'localhost'\n) {\n const sanitizedEnvironment = useMemo(\n () => environment.replace(/:\\d+$/, ''),\n [environment]\n );\n const [state, setState] = useAtom(featureFlagsAtom);\n const [envId, setEnvId] = useState<number | null>(null);\n const debounceTimeout = useRef<NodeJS.Timeout | null>(null);\n const cleanupRef = useRef<(() => void) | null>(null);\n\n const apiKey = passedKey;\n\n useEffect(() => {\n console.log(\n '[use-feature-flags] initializing',\n `environment: ${sanitizedEnvironment}`,\n `apiKey provided: ${Boolean(apiKey)}`\n );\n }, [sanitizedEnvironment, apiKey]);\n const supabase = getSupabase();\n\n const fetchFlags = async () => {\n setState((prev) => ({ ...prev, loading: true }));\n\n console.log('[use-feature-flags] fetching flags for', sanitizedEnvironment);\n\n try {\n const res = await fetch(EDGE_FN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { 'api-key': apiKey } : {}),\n },\n body: JSON.stringify({ environment: sanitizedEnvironment }),\n });\n\n const json = await res.json();\n if (!res.ok) {\n console.warn('Edge function error:', json?.error || res.statusText);\n setState({ flags: [], loading: false });\n return;\n }\n\n const flags = json.flags || [];\n setState({ flags, loading: false });\n console.log('[use-feature-flags] fetched flags', flags);\n\n // Store environment_id from first flag (assumes all have same env)\n if (flags.length > 0 && flags[0].environment_id) {\n setEnvId(flags[0].environment_id);\n }\n } catch (err: any) {\n console.error('Error fetching flags:', err.message);\n setState({ flags: [], loading: false });\n }\n };\n\n useEffect(() => {\n if (!apiKey || initialized) return;\n initialized = true;\n\n fetchFlags();\n\n return () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n if (cleanupRef.current) cleanupRef.current();\n };\n }, [sanitizedEnvironment, apiKey]);\n\n // Subscribe to flag changes for the correct environment\n useEffect(() => {\n if (!envId) return;\n\n const channel = supabase\n .channel(`flags-${sanitizedEnvironment}`)\n .on(\n 'postgres_changes',\n {\n event: '*',\n schema: 'public',\n table: 'feature_flags',\n filter: `environment_id=eq.${envId}`,\n },\n () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n debounceTimeout.current = setTimeout(fetchFlags, 300);\n }\n )\n .subscribe();\n\n if (cleanupRef.current) cleanupRef.current();\n cleanupRef.current = () => supabase.removeChannel(channel);\n\n return () => {\n if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n supabase.removeChannel(channel);\n };\n }, [envId]);\n\n return {\n isActive: (key: string) => {\n const active = state.flags.some((f) => f.key === key && f.enabled === true);\n console.log('[use-feature-flags] isActive', key, active);\n return active;\n },\n flags: state.flags,\n loading: state.loading,\n };\n}\n\n\n\n\n\n"]}
package/package.json CHANGED
@@ -1,29 +1,35 @@
1
1
  {
2
2
  "name": "@geejay/use-feature-flags",
3
- "version": "1.0.12",
4
- "description": "React hook for feature flag management using Supabase",
5
- "main": "dist/index.js",
3
+ "version": "1.0.17",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "import": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "require": "./dist/index.cjs"
10
+ }
11
+ },
12
+ "module": "dist/index.js",
6
13
  "types": "dist/index.d.ts",
7
14
  "files": [
8
15
  "dist",
9
16
  "README.md"
10
17
  ],
11
18
  "scripts": {
12
- "build": "rm -rf dist && tsc -p tsconfig.build.json"
13
- },
14
-
15
- "keywords": ["feature flags", "supabase", "react", "hooks"],
16
- "repository": {
17
- "type": "git",
18
- "url": "https://github.com/your-org/use-feature-flags.git"
19
+ "build": "tsup",
20
+ "prepack": "npm run build"
19
21
  },
20
- "author": "GeeJay",
21
- "license": "MIT",
22
- "dependencies": {
23
- "@supabase/supabase-js": "^2.52.0",
24
- "jotai": "^2.12.5"
22
+ "publishConfig": {
23
+ "access": "public"
25
24
  },
26
25
  "peerDependencies": {
27
26
  "react": ">=16.8"
27
+ },
28
+ "devDependencies": {
29
+ "@supabase/supabase-js": "^2.54.0",
30
+ "@types/react": "^18.3.23",
31
+ "jotai": "^2.13.0",
32
+ "tsup": "^8.5.0",
33
+ "typescript": "^5.9.2"
28
34
  }
29
35
  }
package/dist/store.d.ts DELETED
@@ -1,17 +0,0 @@
1
- export type FeatureFlag = {
2
- id: string;
3
- key: string;
4
- enabled: boolean;
5
- environment: string;
6
- };
7
- export declare const featureFlagsAtom: import("jotai").PrimitiveAtom<{
8
- flags: FeatureFlag[];
9
- loading: boolean;
10
- }> & {
11
- init: {
12
- flags: FeatureFlag[];
13
- loading: boolean;
14
- };
15
- };
16
- export declare function setApiKey(key: string): void;
17
- export declare function getApiKey(): string;
package/dist/store.js DELETED
@@ -1,13 +0,0 @@
1
- import { atom } from 'jotai';
2
- export const featureFlagsAtom = atom({
3
- flags: [],
4
- loading: true,
5
- });
6
- // store.ts
7
- let apiKey = null;
8
- export function setApiKey(key) {
9
- apiKey = key;
10
- }
11
- export function getApiKey() {
12
- return apiKey;
13
- }
@@ -1,8 +0,0 @@
1
- import { FeatureFlag } from './store';
2
- export declare const DEFAULT_SUPABASE_URL = "https://khppgsehvvlukzfdqbuo.supabase.co";
3
- export declare const DEFAULT_SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk";
4
- export declare function useFeatureFlags(passedKey?: string, environment?: string): {
5
- isActive: (key: string) => boolean;
6
- flags: FeatureFlag[];
7
- loading: boolean;
8
- };
@@ -1,96 +0,0 @@
1
- import { useEffect, useMemo, useRef, useState } from 'react';
2
- import { createClient } from '@supabase/supabase-js';
3
- import { featureFlagsAtom } from './store';
4
- import { useAtom } from 'jotai';
5
- export const DEFAULT_SUPABASE_URL = 'https://khppgsehvvlukzfdqbuo.supabase.co';
6
- export const DEFAULT_SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtocHBnc2VodnZsdWt6ZmRxYnVvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI2ODU1MzQsImV4cCI6MjA2ODI2MTUzNH0.8Z4VY4HFMm95UgO21c-DnDkbLPN_0mbDBZJPExaghDk';
7
- const EDGE_FN_URL = `${DEFAULT_SUPABASE_URL}/functions/v1/get-feature-flags`;
8
- let initialized = false;
9
- export function useFeatureFlags(passedKey, environment = window.location.hostname || 'localhost') {
10
- const sanitizedEnvironment = useMemo(() => environment.replace(/:\d+$/, ''), [environment]);
11
- const [state, setState] = useAtom(featureFlagsAtom);
12
- const [envId, setEnvId] = useState(null);
13
- const debounceTimeout = useRef(null);
14
- const cleanupRef = useRef();
15
- const apiKey = passedKey;
16
- const supabase = createClient(DEFAULT_SUPABASE_URL, DEFAULT_SUPABASE_KEY, {
17
- global: {
18
- headers: {
19
- Authorization: `Bearer ${DEFAULT_SUPABASE_KEY}`, // 👈 dynamic access token
20
- },
21
- },
22
- });
23
- const fetchFlags = async () => {
24
- setState((prev) => ({ ...prev, loading: true }));
25
- try {
26
- const res = await fetch(EDGE_FN_URL, {
27
- method: 'POST',
28
- headers: {
29
- 'Content-Type': 'application/json',
30
- 'api-key': apiKey,
31
- },
32
- body: JSON.stringify({ environment: sanitizedEnvironment }),
33
- });
34
- const json = await res.json();
35
- if (!res.ok) {
36
- console.warn('Edge function error:', (json === null || json === void 0 ? void 0 : json.error) || res.statusText);
37
- setState({ flags: [], loading: false });
38
- return;
39
- }
40
- const flags = json.flags || [];
41
- setState({ flags, loading: false });
42
- console.log(`Flags are cool man ${JSON.stringify(flags)}`);
43
- // Store environment_id from first flag (assumes all have same env)
44
- if (flags.length > 0 && flags[0].environment_id) {
45
- setEnvId(flags[0].environment_id);
46
- }
47
- }
48
- catch (err) {
49
- console.error('Error fetching flags:', err.message);
50
- setState({ flags: [], loading: false });
51
- }
52
- };
53
- useEffect(() => {
54
- if (!apiKey || initialized)
55
- return;
56
- initialized = true;
57
- fetchFlags();
58
- return () => {
59
- if (debounceTimeout.current)
60
- clearTimeout(debounceTimeout.current);
61
- if (cleanupRef.current)
62
- cleanupRef.current();
63
- };
64
- }, [sanitizedEnvironment, apiKey]);
65
- // Subscribe to flag changes for the correct environment
66
- useEffect(() => {
67
- if (!envId)
68
- return;
69
- const channel = supabase
70
- .channel(`flags-${sanitizedEnvironment}`)
71
- .on('postgres_changes', {
72
- event: '*',
73
- schema: 'public',
74
- table: 'feature_flags',
75
- filter: `environment_id=eq.${envId}`,
76
- }, () => {
77
- if (debounceTimeout.current)
78
- clearTimeout(debounceTimeout.current);
79
- debounceTimeout.current = setTimeout(fetchFlags, 300);
80
- })
81
- .subscribe();
82
- if (cleanupRef.current)
83
- cleanupRef.current();
84
- cleanupRef.current = () => supabase.removeChannel(channel);
85
- return () => {
86
- if (debounceTimeout.current)
87
- clearTimeout(debounceTimeout.current);
88
- supabase.removeChannel(channel);
89
- };
90
- }, [envId]);
91
- return {
92
- isActive: (key) => state.flags.some((f) => f.key === key && f.enabled === true),
93
- flags: state.flags,
94
- loading: state.loading,
95
- };
96
- }